April 26

Flutter + Claude Code — одна кодовая база на все платформы

Flutter — это когда надо один раз написать и получить iOS, Android, Web и десктоп. Claude Code знает Dart достаточно хорошо, чтобы генерировать идиоматический код, если в CLAUDE.md зафиксировать современный стек: Riverpod 2.x, GoRouter, freezed.

Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper

Стартовый каркас

flutter create my_app
cd my_app
flutter pub add flutter_riverpod go_router dio freezed_annotation json_annotation
flutter pub add --dev build_runner freezed json_serializable

CLAUDE.md

## Стек
- Flutter 3.24+, Dart 3.5+
- Riverpod 2.x (flutter_riverpod, не provider)
- go_router для навигации
- dio для HTTP
- freezed для моделей
- flutter_test + integration_test

## Структура
- lib/features/<name>/ — фича (UI + providers + модели)
- lib/core/ — общее (theme, router, http client)
- lib/main.dart — entry point

## Правила
- Все модели через freezed, никаких ручных fromJson/toJson
- State через Riverpod providers, не setState
- Нет StatefulWidget где можно ConsumerWidget
- Navigator.push запрещён — только GoRouter

Freezed-модель

// lib/features/product/product.dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'product.freezed.dart';
part 'product.g.dart';

@freezed
class Product with _$Product {
  const factory Product({
    required int id,
    required String name,
    required int priceCents,
  }) = _Product;

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

После flutter pub run build_runner build получаешь ==, copyWith, JSON-сериализацию бесплатно.

Riverpod-provider

// lib/features/product/products_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dio/dio.dart';
import 'product.dart';

final dioProvider = Provider((ref) => Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
)));

final productsProvider = FutureProvider<List<Product>>((ref) async {
  final dio = ref.read(dioProvider);
  final res = await dio.get<List<dynamic>>('/products');
  return res.data!.map((e) => Product.fromJson(e)).toList();
});

UI с AsyncValue

// lib/features/product/products_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'products_provider.dart';

class ProductsPage extends ConsumerWidget {
  const ProductsPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final async = ref.watch(productsProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Продукты')),
      body: async.when(
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (e, _) => Center(child: Text('Ошибка: $e')),
        data: (products) => RefreshIndicator(
          onRefresh: () async => ref.invalidate(productsProvider),
          child: ListView.separated(
            itemCount: products.length,
            separatorBuilder: (_, __) => const Divider(height: 0),
            itemBuilder: (_, i) {
              final p = products[i];
              return ListTile(
                title: Text(p.name),
                subtitle: Text('${p.priceCents / 100} ₽'),
                onTap: () => context.push('/product/${p.id}'),
              );
            },
          ),
        ),
      ),
    );
  }
}

Маршрутизация через GoRouter

// lib/core/router.dart
import 'package:go_router/go_router.dart';
import '../features/product/products_page.dart';
import '../features/product/product_detail_page.dart';

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (_, __) => const ProductsPage(),
    ),
    GoRoute(
      path: '/product/:id',
      builder: (_, state) => ProductDetailPage(
        id: int.parse(state.pathParameters['id']!),
      ),
    ),
  ],
);

Сборка

flutter build apk --release                 # Android APK
flutter build appbundle --release           # Play Store
flutter build ios --release                 # iOS (нужен Mac)
flutter build web --release --wasm          # Web с WASM
flutter build macos --release
flutter build windows --release
flutter build linux --release

Подводные камни

  • Claude любит Provider (v4). В CLAUDE.md: «Riverpod 2.x, не provider package».
  • build_runner после каждого @freezed класса. Без него _$Product не существует.
  • iOS сборка только с Mac. Или через Codemagic/Bitrise.
  • Hot reload ломается при изменении enum/freezed. Нужен full restart.

Как попробовать

1. flutter create + pub add зависимостей 2. CLAUDE.md 3. Попроси «список продуктов с Riverpod + Dio + Freezed» 4. flutter run — работает сразу на эмуляторе

Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper