15 Prompts for Flutter: Widgets, State Management, Animations

Introduction

Flutter has become the go-to framework for cross-platform mobile development, but even experienced developers often find themselves spending hours on boilerplate code, debugging state management issues, or tweaking animation curves. As a hands-on developer who uses AI daily, I've compiled a battle-tested collection of prompts that actually speed up real work — from widget composition to Bloc and Riverpod patterns. Each prompt comes with a concrete usage example, so you can copy-paste them into your AI assistant and get production-ready code immediately.

1. StatelessWidget with Custom Styling

Prompt: "Generate a Flutter StatelessWidget that displays a card with rounded corners, a shadow, and an icon on the left. The card must accept a title and subtitle as parameters. Use Theme.of(context) for colors."

Example output (simplified):

class InfoCard extends StatelessWidget {
  final String title;
  final String subtitle;
  final IconData icon;

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

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      child: ListTile(
        leading: Icon(icon, color: Theme.of(context).primaryColor),
        title: Text(title),
        subtitle: Text(subtitle),
      ),
    );
  }
}

2. StatefulWidget with TextFormField Validation

Prompt: "Create a StatefulWidget with a Form and a TextFormField that validates an email address. Show a red error message below the field if invalid. Use a GlobalKey."

Key snippet:

final _formKey = GlobalKey<FormState>();
String _email = '';

// Inside build:
Form(
  key: _formKey,
  child: TextFormField(
    decoration: InputDecoration(labelText: 'Email'),
    validator: (val) {
      if (val == null || !val.contains('@')) return 'Enter a valid email';
      return null;
    },
    onSaved: (val) => _email = val!,
  ),
)

3. AnimatedContainer for Smooth Transitions

Prompt: "Write an AnimatedContainer that changes its width and color when a bool toggles. Duration: 500ms. Use a CurvedAnimation with easeInOut."

Example:

AnimatedContainer(
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
  width: _isExpanded ? 300 : 100,
  height: 50,
  decoration: BoxDecoration(
    color: _isExpanded ? Colors.blue : Colors.red,
    borderRadius: BorderRadius.circular(8),
  ),
)

4. Hero Animation Between Screens

Prompt: "Create a Hero animation between two screens. The first screen has a circular avatar, the second screen shows a full-screen image of the same person. Use identical tags."

Screen 1:

Hero(
  tag: 'avatar',
  child: CircleAvatar(radius: 50, backgroundImage: NetworkImage('https://example.com/avatar.jpg')),
)

Screen 2:

Hero(
  tag: 'avatar',
  child: Image.network('https://example.com/avatar.jpg', fit: BoxFit.contain),
)

5. Bloc Pattern – Counter Example

Prompt: "Generate a simple Bloc counter with events Increment and Decrement. Use flutter_bloc. Include a CounterBloc class and a CounterScreen that listens to states."

CounterBloc:

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<Increment>((event, emit) => emit(state + 1));
    on<Decrement>((event, emit) => emit(state - 1));
  }
}

Screen integration:

BlocProvider(
  create: (_) => CounterBloc(),
  child: CounterScreen(),
);

6. Riverpod – Async Data Fetching

Prompt: "Write a Riverpod provider that fetches a list of users from a REST API using http package. Handle loading and error states in a Consumer widget."

Provider:

final userListProvider = FutureProvider<List<User>>((ref) async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
  if (response.statusCode != 200) throw Exception('Failed to load');
  return (json.decode(response.body) as List).map((e) => User.fromJson(e)).toList();
});

UI:

Consumer(
  builder: (context, ref, _) {
    final usersAsync = ref.watch(userListProvider);
    return usersAsync.when(
      data: (users) => ListView.builder(itemCount: users.length, itemBuilder: ...),
      loading: () => CircularProgressIndicator(),
      error: (e, _) => Text('Error: $e'),
    );
  },
);

7. Custom Animated Widget with AnimationController

Prompt: "Create a custom widget that fades in and slides up when mounted. Use AnimationController and Tween. The widget should start invisible and animate to full opacity and offset zero."

Implementation:

class FadeSlideIn extends StatefulWidget {
  final Widget child;
  final Duration duration;
  const FadeSlideIn({required this.child, this.duration = const Duration(milliseconds: 600)});
  @override
  State<FadeSlideIn> createState() => _FadeSlideInState();
}

class _FadeSlideInState extends State<FadeSlideIn> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _opacity;
  late Animation<Offset> _offset;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration);
    _opacity = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
    _offset = Tween<Offset>(begin: Offset(0, 0.3), end: Offset.zero).animate(_controller);
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) => Opacity(
        opacity: _opacity.value,
        child: Transform.translate(offset: _offset.value, child: child),
      ),
      child: widget.child,
    );
  }
}

8. Provider for Theme Toggle

Prompt: "Use the Provider package to create a theme notifier that toggles between light and dark mode. Provide it at the MaterialApp level."

Model:

class ThemeNotifier extends ChangeNotifier {
  ThemeMode _mode = ThemeMode.light;
  ThemeMode get mode => _mode;

  void toggle() {
    _mode = _mode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
    notifyListeners();
  }
}

Usage:

ChangeNotifierProvider(
  create: (_) => ThemeNotifier(),
  child: Consumer<ThemeNotifier>(
    builder: (context, theme, _) => MaterialApp(
      themeMode: theme.mode,
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
    ),
  ),
);

9. Custom Paint for Drawing

Prompt: "Write a CustomPainter that draws a simple pie chart with 3 segments. Each segment has a different color. The segments are defined by a list of doubles (values)."

Painter:

class PieChartPainter extends CustomPainter {
  final List<double> values;
  final List<Color> colors;

  @override
  void paint(Canvas canvas, Size size) {
    final total = values.reduce((a,b) => a+b);
    double startAngle = -pi / 2;
    final rect = Rect.fromLTWH(0, 0, size.width, size.height);
    for (int i = 0; i < values.length; i++) {
      final sweepAngle = (values[i] / total) * 2 * pi;
      canvas.drawArc(rect, startAngle, sweepAngle, true, Paint()..color = colors[i]);
      startAngle += sweepAngle;
    }
  }

  @override
  bool shouldRepaint(covariant PieChartPainter oldDelegate) => oldDelegate.values != values;
}

10. SliverAppBar with Collapsing Toolbar

Prompt: "Create a CustomScrollView with a SliverAppBar that collapses on scroll. The app bar has a background image and a title that fades in when expanded."

Implementation:

CustomScrollView(
  slivers: [
    SliverAppBar(
      expandedHeight: 200,
      flexibleSpace: FlexibleSpaceBar(
        background: Image.network('https://example.com/cover.jpg', fit: BoxFit.cover),
        title: Text('My Profile'),
      ),
      pinned: true,
    ),
    SliverList(delegate: SliverChildBuilderDelegate(..., childCount: 20)),
  ],
);

11. StreamBuilder for Real-Time Data

Prompt: "Use StreamBuilder to display a list of messages from a Firestore collection. Show a loading indicator while waiting, and a 'No messages' text when empty."

Code:

StreamBuilder<QuerySnapshot>(
  stream: FirebaseFirestore.instance.collection('messages').snapshots(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) return CircularProgressIndicator();
    if (!snapshot.hasData || snapshot.data!.docs.isEmpty) return Text('No messages');
    return ListView.builder(
      itemCount: snapshot.data!.docs.length,
      itemBuilder: (context, index) => Text(snapshot.data!.docs[index]['text']),
    );
  },
);

12. Staggered Animation with Multiple Controllers

Prompt: "Create a staggered animation where three containers slide in from the left one after another, each delayed by 200ms. Use a single AnimationController."

Key logic:

final List<Interval> intervals = [
  Interval(0.0, 0.3),
  Interval(0.2, 0.5),
  Interval(0.4, 0.7),
];

// For each item:
SlideTransition(
  position: Tween<Offset>(
    begin: Offset(-1, 0),
    end: Offset.zero,
  ).animate(CurvedAnimation(parent: _controller, curve: intervals[i])),
  child: ...
)

13. Internationalization (i18n) with Provider

Prompt: "Implement locale switching using Provider and the intl package. Provide a LocaleNotifier that can change the locale and rebuilds the MaterialApp."

Notifier:

class LocaleNotifier extends ChangeNotifier {
  Locale _locale = Locale('en');
  Locale get locale => _locale;
  void setLocale(Locale locale) {
    _locale = locale;
    notifyListeners();
  }
}

Usage:

Consumer<LocaleNotifier>(
  builder: (context, localeNotifier, _) => MaterialApp(
    locale: localeNotifier.locale,
    localizationsDelegates: [
      AppLocalizations.delegate,
      GlobalMaterialLocalizations.delegate,
    ],
    supportedLocales: [Locale('en'), Locale('ru')],
  ),
);

14. Responsive Layout with LayoutBuilder

Prompt: "Write a LayoutBuilder that returns a Row on wide screens and a Column on narrow screens (breakpoint at 600px). The children are an image and a text block."

Code:

LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      return Row(children: [Image(...), Text('...')]);
    } else {
      return Column(children: [Image(...), Text('...')]);
    }
  },
);

15. Testing a Bloc with Fake Data

Prompt: "Write a unit test for a CounterBloc using bloc_test. Test that Increment event increases state by 1 and Decrement decreases by 1."

Test:

blocTest<CounterBloc, int>(
  'emits [1] when Increment is added',
  build: () => CounterBloc(),
  act: (bloc) => bloc.add(Increment()),
  expect: () => [1],
);

blocTest<CounterBloc, int>(
  'emits [-1] when Decrement is added',
  build: () => CounterBloc(),
  act: (bloc) => bloc.add(Decrement()),
  expect: () => [-1],
);

Conclusion

These 15 prompts cover the most common Flutter tasks I encounter daily. By feeding them to your AI assistant, you can cut boilerplate generation time by half and focus on the unique logic of your app. The key is to be specific about widget names, state management patterns, and animation parameters. Copy, paste, customize — and ship faster.

ASI Biont supports connecting your Flutter app to external services like Firebase or REST APIs through custom integrations — learn more at asibiont.com/courses.

← All posts

Comments