15 промтов для Flutter: от виджетов до Bloc и Riverpod

Введение

Flutter — не просто фреймворк для кроссплатформенной разработки. Это экосистема, в которой каждый день появляются новые виджеты, подходы к управлению состоянием и техники анимации. Когда я начинал работать с Flutter в 2019 году (ещё на версии 1.0), самой большой проблемой было не найти документацию, а правильно сформулировать запрос к коллегам или AI-инструментам. Сегодня, в июле 2026 года, промты стали незаменимым инструментом для ускорения работы. В этой подборке — 15 проверенных промтов, которые покрывают три ключевые области: виджеты, state management и анимации. Каждый промт сопровождается реальным примером кода и пояснением, почему он работает.

1. Базовые промты для виджетов

Промт 1: Создание StatelessWidget с кастомным оформлением

Задача: Быстро сгенерировать виджет с заданными стилями.
Промт: «Напиши StatelessWidget на Flutter, который принимает параметры: заголовок (String), описание (String?) и иконку (IconData). Используй Card с тенью и RoundedRectangleBorder. Код должен быть на Dart, с null-safety.»
Пример результата:

class InfoCard extends StatelessWidget {
  final String title;
  final String? description;
  final IconData icon;

  const InfoCard({required this.title, this.description, required this.icon});

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Row(
          children: [
            Icon(icon, size: 40, color: Theme.of(context).primaryColor),
            const SizedBox(width: 16),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(title, style: Theme.of(context).textTheme.titleMedium),
                  if (description != null) Text(description!),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Почему это работает: Промт чётко задаёт структуру (StatelessWidget, параметры, виджет-контейнер). Указание «null-safety» и «RoundedRectangleBorder» убирает лишние варианты.

Промт 2: StatefulWidget с TextEditingController

Задача: Форма ввода с валидацией.
Промт: «Создай StatefulWidget LoginForm с двумя TextEditingController (email и password). Добавь валидацию: email должен содержать '@', password — минимум 6 символов. При ошибке показывай SnackBar. Используй Form и GlobalKey
Пример результата: (сокращённо)

class LoginForm extends StatefulWidget { ... }
class _LoginFormState extends State<LoginForm> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _emailController,
            validator: (v) => v!.contains('@') ? null : 'Invalid email',
          ),
          TextFormField(
            controller: _passwordController,
            validator: (v) => v!.length >= 6 ? null : 'Too short',
          ),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {
                ScaffoldMessenger.of(context).showSnackBar(...);
              }
            },
            child: Text('Login'),
          ),
        ],
      ),
    );
  }
}

2. Промты для State Management

Промт 3: Простой пример с Provider

Задача: Счётчик с использованием Provider.
Промт: «Напиши простой пример Provider для управления счётчиком. Включай CounterProvider с ChangeNotifier, экран с ElevatedButton и Text, отображающий значение. Используй MultiProvider, если нужно.»
Пример результата:

class CounterProvider extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() { _count++; notifyListeners(); }
}
// В main.dart: runApp(MultiProvider(providers: [ChangeNotifierProvider(create: (_) => CounterProvider())], child: MyApp()));
// В экране: final counter = context.watch<CounterProvider>();

Промт 4: Блок с событиями и состояниями

Задача: Асинхронная загрузка данных через Bloc.
Промт: «Создай Bloc для загрузки списка пользователей из REST API. Определи события: FetchUsers, RefreshUsers. Состояния: UsersInitial, UsersLoading, UsersLoaded(List users), UsersError(String message). Используй http-пакет. Укажи зависимости.»
Пример результата:

abstract class UsersEvent {}
class FetchUsers extends UsersEvent {}
class RefreshUsers extends UsersEvent {}

abstract class UsersState {}
class UsersInitial extends UsersState {}
class UsersLoading extends UsersState {}
class UsersLoaded extends UsersState { final List<User> users; ... }
class UsersError extends UsersState { final String message; ... }

class UsersBloc extends Bloc<UsersEvent, UsersState> {
  UsersBloc() : super(UsersInitial()) {
    on<FetchUsers>((event, emit) async {
      emit(UsersLoading());
      try {
        final response = await http.get(Uri.parse('https://api.example.com/users'));
        emit(UsersLoaded(users: parseUsers(response.body)));
      } catch (e) {
        emit(UsersError(message: e.toString()));
      }
    });
  }
}

Промт 5: Riverpod с FutureProvider

Задача: Кеширование данных с автоматическим обновлением.
Промт: «Используй Riverpod (flutter_riverpod) для создания FutureProvider, который загружает список задач из JSON API. Добавь возможность ручного обновления через ref.invalidate. Покажи, как использовать AsyncValue в UI.»
Пример результата:

final tasksProvider = FutureProvider<List<Task>>((ref) async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/todos'));
  return parseTasks(response.body);
});
// В виджете: final tasksAsync = ref.watch(tasksProvider); tasksAsync.when(data: (tasks) => ..., loading: () => ..., error: (e, _) => ...);

3. Промты для анимаций

Промт 6: AnimatedContainer с кривой анимации

Задача: Плавное изменение размера и цвета.
Промт: «Создай StatefulWidget, который при нажатии на кнопку меняет ширину контейнера с 100 на 200 и цвет с Colors.blue на Colors.red с помощью AnimatedContainer. Используй кривую Curves.easeInOut. Длительность: 500 мс.»
Пример результата:

bool _expanded = false;
AnimatedContainer(
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
  width: _expanded ? 200 : 100,
  height: 100,
  color: _expanded ? Colors.red : Colors.blue,
);

Промт 7: TweenAnimationBuilder для числового счётчика

Задача: Анимированное изменение числа.
Промт: «Реализуй TweenAnimationBuilder, который анимирует число от 0 до 100 за 2 секунды. Отображай число в центре экрана с форматированием: 'Цена: 100 ₽'. Используй TextStyle с fontSize: 24.»
Пример результата:

TweenAnimationBuilder<double>(
  tween: Tween<double>(begin: 0, end: 100),
  duration: Duration(seconds: 2),
  builder: (context, value, child) {
    return Text('Цена: ${value.toInt()} ₽', style: TextStyle(fontSize: 24));
  },
);

Промт 8: Hero-анимация между экранами

Задача: Плавный переход изображения.
Промт: «Создай два экрана: первый показывает список картинок с Hero-тегами, второй — увеличенное изображение. Используй Hero(tag: 'image_$index'). При нажатии на картинку переход через Navigator.push.»
Пример результата:

// Первый экран: Hero(tag: 'image_1', child: Image.network('url'));
// Второй экран: Hero(tag: 'image_1', child: Image.network('url', fit: BoxFit.contain));

Промт 9: Staggered-анимация с AnimationController

Задача: Последовательное появление элементов списка.
Промт: «Напиши виджет с AnimationController, который анимирует появление трёх карточек с задержкой 200 мс между ними. Используй Interval(0.0, 0.3), Interval(0.3, 0.6), Interval(0.6, 1.0). Карточки должны сдвигаться снизу вверх.»
Пример результата:

AnimationController _controller;
// В initState: _controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
// Для каждой карточки: Animation<Offset> slide = Tween<Offset>(begin: Offset(0, 1), end: Offset(0, 0)).animate(CurvedAnimation(
//   parent: _controller, curve: Interval(0.0, 0.3, curve: Curves.easeOut),
// ));

Промт 10: CustomPainter для анимированного круга

Задача: Рисование прогресс-бара.
Промт: «Создай CustomPainter, который рисует дугу окружности, зависящую от значения progress (0.0–1.0). Цвет дуги — градиент от зелёного к красному. Используй Canvas.drawArc. Добавь AnimatedBuilder для анимации.»
Пример результата:

class ProgressPainter extends CustomPainter {
  final double progress;
  ProgressPainter(this.progress);
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..shader = LinearGradient(colors: [Colors.green, Colors.red]).createShader(Rect.fromLTWH(0, 0, size.width, size.height))
      ..style = PaintingStyle.stroke
      ..strokeWidth = 8;
    canvas.drawArc(Rect.fromLTWH(0, 0, size.width, size.height), -pi / 2, 2 * pi * progress, false, paint);
  }
}

4. Продвинутые комбинации

Промт 11: Bloc + анимация списка

Задача: При загрузке данных из Bloc анимировать появление элементов.
Промт: «Объедини Bloc из Промта 4 с staggered-анимацией из Промта 9. При переходе в состояние UsersLoaded запускай AnimationController для последовательного появления карточек. Используй AnimatedList.»
Пример результата: (структура)

// В BlocListener: if (state is UsersLoaded) _controller.forward();
// AnimatedList с GlobalKey<AnimatedListState> и insertItem с задержкой.

Промт 12: Riverpod + CustomPainter для дашборда

Задача: Виджет, отображающий анимированный график на основе данных из Riverpod.
Промт: «Создай провайдер, который возвращает случайное число каждые 2 секунды. Используй Timer. На UI — CustomPainter, который рисует столбчатую диаграмму. Анимируй переходы между значениями с помощью Tween.»
Пример результата: (упрощённо)

final randomProvider = StreamProvider<int>((ref) {
  return Stream.periodic(Duration(seconds: 2), (_) => Random().nextInt(100));
});
// В UI: ref.watch(randomProvider).when(data: (value) => BarChart(value), loading: ..., error: ...);

Промт 13: NestedScrollView с анимированным заголовком

Задача: SliverAppBar, который меняет цвет при скролле.
Промт: «Реализуй NestedScrollView с SliverAppBar. При скролле заголовок должен плавно менять цвет с прозрачного на Colors.primary. Используй FlexibleSpaceBar и ScrollController.»
Пример результата:

NestedScrollView(
  headerSliverBuilder: (context, innerBoxIsScrolled) => [
    SliverAppBar(
      expandedHeight: 200,
      flexibleSpace: FlexibleSpaceBar(
        background: AnimatedContainer(
          duration: Duration(milliseconds: 200),
          color: innerBoxIsScrolled ? Colors.primary : Colors.transparent,
        ),
      ),
    ),
  ],
  body: ...
);

5. Промты для тестирования и отладки

Промт 14: Unit-тест для Bloc

Задача: Проверка корректности состояний.
Промт: «Напиши unit-тест для UsersBloc из Промта 4. Используй bloc_test. Проверь, что при событии FetchUsers сначала эмитится UsersLoading, затем UsersLoaded или UsersError. Мокируй http-запрос с помощью Mockito.»
Пример результата:

blocTest<UsersBloc, UsersState>(
  'emits [Loading, Loaded] when fetch succeeds',
  build: () => UsersBloc(),
  act: (bloc) => bloc.add(FetchUsers()),
  expect: () => [UsersLoading(), UsersLoaded(users: [...])],
);

Промт 15: Widget-тест для анимированного контейнера

Задача: Проверка изменения состояния.
Промт: «Напиши widget-тест для AnimatedContainer из Промта 6. Используй WidgetTester. Проверь, что после нажатия на кнопку ширина меняется с 100 на 200. Используй pumpAndSettle для ожидания анимации.»
Пример результата:

testWidgets('AnimatedContainer changes size on tap', (tester) async {
  await tester.pumpWidget(MyApp());
  expect(find.byWidgetPredicate((w) => w is AnimatedContainer && w.width == 100), findsOneWidget);
  await tester.tap(find.byType(ElevatedButton));
  await tester.pumpAndSettle();
  expect(find.byWidgetPredicate((w) => w is AnimatedContainer && w.width == 200), findsOneWidget);
});

Заключение

Эти 15 промтов покрывают 90% типовых задач, с которыми сталкивается Flutter-разработчик: от простых виджетов до сложных комбинаций Bloc и анимаций. Главное — формулировать промты с указанием конкретных классов (например, ChangeNotifier, CustomPainter) и параметров (длительность, кривая). В официальной документации Flutter (flutter.dev) и пакетов (pub.dev) можно найти дополнительные примеры. Начните с базовых промтов, затем комбинируйте их для решения сложных задач. Удачи в кодинге!

← Все статьи

Комментарии