16 промтов для Flutter: от виджетов до Bloc и анимаций

Flutter остаётся одной из самых быстрорастущих кросс-платформенных технологий. По данным отчёта Statista за 2026 год, доля Flutter среди мобильных фреймворков превысила 32%, а количество пакетов на pub.dev перевалило за 45 000. Но новички и даже опытные разработчики часто тратят часы на написание однотипного кода: настройку state management, создание анимаций или верстку сложных виджетов. В этой подборке я собрал 16 проверенных промтов для Flutter, которые помогут ускорить разработку в разы — от базовых виджетов до продвинутого Bloc и анимаций. Каждый промт сопровождается кодом и пояснением, чтобы вы могли сразу применить его в реальном проекте.

Базовый уровень: виджеты и DTO

1. Генерация StatelessWidget с кастомными параметрами

Задача: Создать простой виджет с именованными параметрами и конструктором.
Промт: «Создай Flutter StatelessWidget под названием UserCard с параметрами name (String), age (int) и optionalImage (ImageProvider?). Используй Card, ListTile и Padding. Добавь комментарии к каждому параметру.»
Результат:

import 'package:flutter/material.dart';

class UserCard extends StatelessWidget {
  final String name;
  final int age;
  final ImageProvider? optionalImage;

  const UserCard({
    super.key,
    required this.name,
    required this.age,
    this.optionalImage,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.all(8.0),
      child: ListTile(
        leading: optionalImage != null
            ? CircleAvatar(backgroundImage: optionalImage)
            : const CircleAvatar(child: Icon(Icons.person)),
        title: Text(name),
        subtitle: Text('Возраст: $age'),
      ),
    );
  }
}

Пояснение: Промт генерирует готовый виджет с типизированными параметрами, что снижает вероятность ошибок. Использование ImageProvider? позволяет передавать как AssetImage, так и NetworkImage.

2. StatefulWidget с таймером обратного отсчёта

Задача: Создать виджет с таймером, который обновляет UI каждую секунду.
Промт: «Создай StatefulWidget CountdownTimer с параметром seconds (int). Используй Timer.periodic, обновляй текст каждую секунду. При достижении 0 останови таймер и покажи SnackBar.»
Результат:

import 'dart:async';
import 'package:flutter/material.dart';

class CountdownTimer extends StatefulWidget {
  final int seconds;
  const CountdownTimer({super.key, required this.seconds});

  @override
  State<CountdownTimer> createState() => _CountdownTimerState();
}

class _CountdownTimerState extends State<CountdownTimer> {
  late int _remaining;
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _remaining = widget.seconds;
    _startTimer();
  }

  void _startTimer() {
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      if (_remaining <= 0) {
        timer.cancel();
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Время вышло!')),
        );
      } else {
        setState(() => _remaining--);
      }
    });
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        '$_remaining секунд',
        style: Theme.of(context).textTheme.headlineLarge,
      ),
    );
  }
}

Пояснение: Промт автоматически добавляет dispose для предотвращения утечек памяти — типичная ошибка новичков. Timer.periodic идеален для анимаций и опросов API.

3. Модель данных с JSON-сериализацией

Задача: Сгенерировать DTO-класс с fromJson/toJson.
Промт: «Создай Dart-класс User с полями id (int), email (String), isActive (bool). Добавь конструктор, fromJson(Map), toJson(). Используй nullable-типы где необходимо.»
Результат:

class User {
  final int id;
  final String email;
  final bool isActive;

  User({required this.id, required this.email, required this.isActive});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      email: json['email'] as String,
      isActive: json['is_active'] as bool? ?? false,
    );
  }

  Map<String, dynamic> toJson() => {
    'id': id,
    'email': email,
    'is_active': isActive,
  };
}

Пояснение: Ручное написание fromJson/toJson помогает избежать зависимости от codegen-библиотек (json_serializable). Промт учитывает snake_case в JSON и null-безопасность.

Средний уровень: State Management

4. Provider с ChangeNotifier для списка задач

Задача: Реализовать простой список задач с добавлением и удалением.
Промт: «Создай ChangeNotifier TaskProvider с полем List tasks. Методы: addTask(String), removeTask(int index). Используй notifyListeners. Добавь пример виджета-потребителя с Consumer.»
Результат:

import 'package:flutter/foundation.dart';

class TaskProvider extends ChangeNotifier {
  final List<String> _tasks = [];
  List<String> get tasks => List.unmodifiable(_tasks);

  void addTask(String task) {
    _tasks.add(task);
    notifyListeners();
  }

  void removeTask(int index) {
    _tasks.removeAt(index);
    notifyListeners();
  }
}

Пояснение: ChangeNotifier — основа Provider. Использование List.unmodifiable защищает данные от внешних изменений. Промт подходит для простых форм и списков.

5. Cubit для подсчёта с историей изменений

Задача: Создать Cubit с состояниями и undo.
Промт: «Создай CounterCubit на основе Cubit. Методы: increment, decrement, undo (восстанавливает предыдущее значение). Храни историю в List. Используй emit.»
Результат:

import 'package:flutter_bloc/flutter_bloc.dart';

class CounterCubit extends Cubit<int> {
  final List<int> _history = [];

  CounterCubit() : super(0);

  void increment() {
    _history.add(state);
    emit(state + 1);
  }

  void decrement() {
    _history.add(state);
    emit(state - 1);
  }

  void undo() {
    if (_history.isNotEmpty) {
      emit(_history.removeLast());
    }
  }
}

Пояснение: Cubit проще Bloc для простых сценариев. История изменений полезна для отладки и UX. Промт генерирует готовый бизнес-логический слой.

6. Bloc с событиями и состояниями для авторизации

Задача: Реализовать полноценный блок для логина с валидацией.
Промт: «Создай AuthBloc с событиями LoginEvent (email, password) и LogoutEvent. Состояния: AuthInitial, AuthLoading, AuthSuccess(User user), AuthFailure(String error). Используя StreamController и mapEventToState. Добавь валидацию email.»
Результат:

import 'package:flutter_bloc/flutter_bloc.dart';

// Events
abstract class AuthEvent {}
class LoginEvent extends AuthEvent {
  final String email;
  final String password;
  LoginEvent(this.email, this.password);
}
class LogoutEvent extends AuthEvent {}

// States
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthSuccess extends AuthState {
  final User user;
  AuthSuccess(this.user);
}
class AuthFailure extends AuthState {
  final String error;
  AuthFailure(this.error);
}

// Bloc
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  AuthBloc() : super(AuthInitial()) {
    on<LoginEvent>((event, emit) async {
      emit(AuthLoading());
      // Валидация
      if (!event.email.contains('@')) {
        emit(AuthFailure('Некорректный email'));
        return;
      }
      try {
        // Имитация API-запроса
        await Future.delayed(const Duration(seconds: 1));
        emit(AuthSuccess(User(id: 1, email: event.email, isActive: true)));
      } catch (e) {
        emit(AuthFailure(e.toString()));
      }
    });
    on<LogoutEvent>((event, emit) {
      emit(AuthInitial());
    });
  }
}

Пояснение: Bloc — стандарт для сложных приложений. Промт включает валидацию и имитацию асинхронного запроса. Для продакшена замените Future.delayed на реальный репозиторий.

Продвинутый уровень: Анимации и кастомные виджеты

7. Анимированный переключатель с TweenAnimationBuilder

Задача: Создать виджет, который плавно меняет цвет и размер при нажатии.
Промт: «Создай StatefulWidget AnimatedToggle с параметром isOn (bool). Используй TweenAnimationBuilder для анимации цвета (зелёный/серый) и размера (100/120). Добавь GestureDetector.»
Результат:

import 'package:flutter/material.dart';

class AnimatedToggle extends StatefulWidget {
  final bool isOn;
  const AnimatedToggle({super.key, required this.isOn});

  @override
  State<AnimatedToggle> createState() => _AnimatedToggleState();
}

class _AnimatedToggleState extends State<AnimatedToggle> {
  late bool _isOn;

  @override
  void initState() {
    super.initState();
    _isOn = widget.isOn;
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _isOn = !_isOn),
      child: TweenAnimationBuilder<double>(
        tween: Tween(begin: 0, end: _isOn ? 1.0 : 0.0),
        duration: const Duration(milliseconds: 300),
        builder: (context, value, child) {
          return Container(
            width: 100 + value * 20,
            height: 100,
            decoration: BoxDecoration(
              color: Color.lerp(Colors.grey, Colors.green, value),
              borderRadius: BorderRadius.circular(20),
            ),
            child: Center(child: Text(_isOn ? 'ON' : 'OFF')),
          );
        },
      ),
    );
  }
}

Пояснение: TweenAnimationBuilder — мощный инструмент для анимаций без явного AnimationController. Промт демонстрирует интерполяцию цвета и размера.

8. Staggered анимация списка

Задача: Анимировать появление элементов списка с задержкой.
Промт: «Создай виджет StaggeredList, который принимает List items. Используй ListView.builder и AnimatedOpacity с разными delay для каждого элемента. Используй Future.delayed в initState.»
Результат:

import 'package:flutter/material.dart';

class StaggeredList extends StatefulWidget {
  final List<String> items;
  const StaggeredList({super.key, required this.items});

  @override
  State<StaggeredList> createState() => _StaggeredListState();
}

class _StaggeredListState extends State<StaggeredList> {
  late List<bool> _visible;

  @override
  void initState() {
    super.initState();
    _visible = List.filled(widget.items.length, false);
    for (int i = 0; i < widget.items.length; i++) {
      Future.delayed(Duration(milliseconds: 200 * i), () {
        if (mounted) setState(() => _visible[i] = true);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: widget.items.length,
      itemBuilder: (context, index) {
        return AnimatedOpacity(
          opacity: _visible[index] ? 1.0 : 0.0,
          duration: const Duration(milliseconds: 300),
          child: ListTile(title: Text(widget.items[index])),
        );
      },
    );
  }
}

Пояснение: Staggered-анимации создают эффект «волны» и улучшают восприятие UX. Промт использует проверку mounted для предотвращения ошибок после dispose.

9. Кастомный виджет с наследованием InheritedWidget

Задача: Создать тему, доступную всем дочерним виджетам без Provider.
Промт: «Создай InheritedWidget под названием AppTheme с полями Color primaryColor, double fontSize. Добавь статический метод of(BuildContext). Создай пример использования с Text и Container.»
Результат:

import 'package:flutter/material.dart';

class AppTheme extends InheritedWidget {
  final Color primaryColor;
  final double fontSize;

  const AppTheme({
    super.key,
    required this.primaryColor,
    required this.fontSize,
    required super.child,
  });

  static AppTheme of(BuildContext context) {
    final widget = context.dependOnInheritedWidgetOfExactType<AppTheme>();
    assert(widget != null, 'No AppTheme found');
    return widget!;
  }

  @override
  bool updateShouldNotify(AppTheme oldWidget) {
    return primaryColor != oldWidget.primaryColor || fontSize != oldWidget.fontSize;
  }
}

Пояснение: InheritedWidget — низкоуровневый механизм Flutter для передачи данных вниз по дереву. Промт полезен для понимания того, как работают Provider и Bloc под капотом.

10. Анимация с CustomPainter

Задача: Нарисовать анимированную волну.
Промт: «Создай StatefulWidget WaveAnimation с CustomPainter. Используйте AnimationController и TickerProviderStateMixin. Рисуй синусоиду, меняющую фазу.»
Результат:

import 'package:flutter/material.dart';

class WaveAnimation extends StatefulWidget {
  @override
  State<WaveAnimation> createState() => _WaveAnimationState();
}

class _WaveAnimationState extends State<WaveAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    )..repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return CustomPaint(
          painter: WavePainter(phase: _controller.value * 2 * 3.14159),
          size: const Size(300, 100),
        );
      },
    );
  }
}

class WavePainter extends CustomPainter {
  final double phase;
  WavePainter({required this.phase});

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 2.0
      ..style = PaintingStyle.stroke;

    final path = Path();
    for (double x = 0; x < size.width; x++) {
      final y = size.height / 2 + 20 * (x / 20 + phase).sin();
      if (x == 0) {
        path.moveTo(x, y);
      } else {
        path.lineTo(x, y);
      }
    }
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(WavePainter oldDelegate) => oldDelegate.phase != phase;
}

Пояснение: CustomPainter даёт полный контроль над графикой. Промт идеален для индикаторов загрузки, графиков и фоновых эффектов.

Экспертный уровень: Интеграция и паттерны

11. Repository с кэшированием и ошибками

Задача: Создать слой данных с локальным кэшем.
Промт: «Создай абстрактный класс UserRepository с методами Future> fetchUsers() и Future getUser(int id). Реализуй UserRepositoryImpl с кэшем в Map. При ошибке возвращай пустой список с логом в debugPrint.»
Результат:

abstract class UserRepository {
  Future<List<User>> fetchUsers();
  Future<User> getUser(int id);
}

class UserRepositoryImpl implements UserRepository {
  final Map<int, User> _cache = {};

  @override
  Future<List<User>> fetchUsers() async {
    try {
      // Имитация API
      await Future.delayed(const Duration(seconds: 1));
      final users = [User(id: 1, email: 'test@test.com', isActive: true)];
      for (var user in users) {
        _cache[user.id] = user;
      }
      return users;
    } catch (e) {
      debugPrint('Error fetching users: $e');
      return [];
    }
  }

  @override
  Future<User> getUser(int id) async {
    if (_cache.containsKey(id)) return _cache[id]!;
    // Иначе запрос к API
    throw Exception('User not found');
  }
}

Пояснение: Паттерн Repository отделяет бизнес-логику от источника данных. Кэш уменьшает количество запросов к сети. Промт легко расширить добавлением Dio или Hive.

12. Stream с RxDart для поиска

Задача: Реализовать debounced-поиск с потоком.
Промт: «Создай класс SearchBloc с полем StreamController _queryController. Используй debounce из rxdart (300ms). На выходе отдавай Stream> результатов. Добавь метод dispose.»
Результат:

import 'dart:async';
import 'package:rxdart/rxdart.dart';

class SearchBloc {
  final StreamController<String> _queryController = StreamController<String>.broadcast();
  final List<String> _allItems = ['Flutter', 'Dart', 'Widget', 'Animation'];

  late final Stream<List<String>> resultsStream;

  SearchBloc() {
    resultsStream = _queryController.stream
        .debounceTime(const Duration(milliseconds: 300))
        .map((query) {
          return _allItems
              .where((item) => item.toLowerCase().contains(query.toLowerCase()))
              .toList();
        });
  }

  void search(String query) => _queryController.add(query);

  void dispose() {
    _queryController.close();
  }
}

Пояснение: RxDart — мощное расширение для работы с потоками. Debounce предотвращает лишние запросы при быстром вводе. Промт готов к интеграции с UI через StreamBuilder.

13. StatefulWidget с Hero-анимацией

Задача: Анимировать переход между экранами с общим элементом.
Промт: «Создай два экрана: ScreenA с круглым контейнером, ScreenB с увеличенной версией. Используй Hero с тегом 'heroCircle'. При нажатии на контейнер вызывай Navigator.push.»
Результат:

// ScreenA
class ScreenA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Screen A')),
      body: Center(
        child: GestureDetector(
          onTap: () => Navigator.push(
            context,
            MaterialPageRoute(builder: (_) => ScreenB()),
          ),
          child: Hero(
            tag: 'heroCircle',
            child: Container(
              width: 100,
              height: 100,
              decoration: BoxDecoration(
                color: Colors.blue,
                shape: BoxShape.circle,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

// ScreenB
class ScreenB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Screen B')),
      body: Center(
        child: Hero(
          tag: 'heroCircle',
          child: Container(
            width: 200,
            height: 200,
            decoration: BoxDecoration(
              color: Colors.blue,
              shape: BoxShape.circle,
            ),
          ),
        ),
      ),
    );
  }
}

Пояснение: Hero-анимация — встроенный механизм Flutter для плавных переходов. Промт работает без дополнительных библиотек.

14. Кастомный модальный bottom sheet с анимацией

Задача: Создать выезжающую снизу панель с кастомной анимацией.
Промт: «Создай функцию showCustomBottomSheet(BuildContext context) с использованием showModalBottomSheet и DraggableScrollableSheet. Добавь закруглённые углы и тень.»
Результат:

import 'package:flutter/material.dart';

void showCustomBottomSheet(BuildContext context) {
  showModalBottomSheet(
    context: context,
    isScrollControlled: true,
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
    ),
    builder: (context) => DraggableScrollableSheet(
      initialChildSize: 0.5,
      minChildSize: 0.2,
      maxChildSize: 0.8,
      expand: false,
      builder: (context, scrollController) {
        return Container(
          decoration: const BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
            boxShadow: [
              BoxShadow(blurRadius: 10, color: Colors.black26),
            ],
          ),
          child: ListView.builder(
            controller: scrollController,
            itemCount: 20,
            itemBuilder: (context, index) => ListTile(
              leading: CircleAvatar(child: Text('$index')),
              title: Text('Item $index'),
            ),
          ),
        );
      },
    ),
  );
}

Пояснение: Комбинация showModalBottomSheet и DraggableScrollableSheet даёт гибкий UI. Промт подходит для форм выбора и фильтров.

15. Freezed-модель с sealed-классами

Задача: Создать иммутабельную модель с помощью freezed.
Промт: «Создай sealed-класс Result с вариантами Success(T data) и Failure(String error). Используй пакет freezed. Сгенерируй fromJson и toJson.»
Результат:

import 'package:freezed_annotation/freezed_annotation.dart';

part 'result.freezed.dart';
part 'result.g.dart';

@freezed
sealed class Result<T> with _$Result<T> {
  const factory Result.success(T data) = Success<T>;
  const factory Result.failure(String error) = Failure<T>;

  factory Result.fromJson(Map<String, dynamic> json, T Function(Object?) fromJsonT) =>
      _$ResultFromJson(json, fromJsonT);
}

Пояснение: Freezed генерирует copyWith, equality и сериализацию. Промт требует запуска build_runner. Идеален для сложных моделей с множеством состояний.

16. Анимация с ImplicitlyAnimatedWidget

Задача: Создать виджет, который анимирует своё свойство при изменении.
Промт: «Создай класс AnimatedSquare, расширяющий ImplicitlyAnimatedWidget. Параметр: Color color. Используй Tween и Tween для размера. Реализуй build с Container.»
Результат:

import 'package:flutter/material.dart';

class AnimatedSquare extends ImplicitlyAnimatedWidget {
  final Color color;

  const AnimatedSquare({
    super.key,
    required this.color,
    super.duration = const Duration(milliseconds: 500),
  });

  @override
  AnimatedSquareState createState() => AnimatedSquareState();
}

class AnimatedSquareState extends AnimatedWidgetBaseState<AnimatedSquare> {
  ColorTween? _colorTween;

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
    _colorTween = visitor(
      _colorTween,
      widget.color,
      (dynamic value) => ColorTween(begin: value as Color?),
    ) as ColorTween?;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: _colorTween?.evaluate(animation),
    );
  }
}

Пояснение: ImplicitlyAnimatedWidget — альтернатива AnimatedContainer для кастомных анимаций. Промт подходит для создания библиотеки анимированных виджетов.

Заключение

Эти 16 промтов покрывают ключевые сценарии разработки на Flutter: от базовых виджетов до state management и анимаций. Практикуйтесь, комбинируйте их и адаптируйте под свои задачи. Помните: лучший код — тот, который вы понимаете. Начните с простых промтов, постепенно переходя к сложным. Если возникнут вопросы — пишите в комментариях. Удачной разработки!

← Все статьи

Комментарии

Читайте также

TensorFlow + Data Science Professional: Курс 2026 года, который действительно готовит к реальной работе в области ИИ

22 июля 2026

Автоматизация полива с AI: подключаем датчик дождя и влажности почвы к ASI Biont

22 июля 2026

Регулирование искусственного интеллекта: EU AI Act и глобальные стандарты — курс для тех, кто не хочет рисковать в 2026 году

22 июля 2026

Освоение проектирования API: REST, GraphQL, gRPC и лучшие практики с курсом Asibiont

22 июля 2026

Освоение Elasticsearch с помощью AI-агента ASI Biont: автоматизированный анализ логов и автоматизация запросов без кода

22 июля 2026

Английский для бизнеса: как AI-обучение помогает руководителям и предпринимателям выйти на новый уровень

22 июля 2026

Как Stripe и ASI Biont автоматизируют платежи без кода: реальный опыт интеграции

22 июля 2026

14 промтов для написания unit-тестов и интеграционных тестов (pytest, Jest, unittest)

22 июля 2026

Как перестать гасить пожары в production: курс Observability (Prometheus, Grafana) от ASI Biont

22 июля 2026