guided_flow 1.0.0 copy "guided_flow: ^1.0.0" to clipboard
guided_flow: ^1.0.0 copied to clipboard

Interactive guided tours for Flutter. Users tap the real widgets: spotlights are tap-through, work across bottom sheets and pushed routes, and advance automatically.

example/lib/main.dart

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

/// A four-step tour across three surfaces — a page, a bottom sheet, and a
/// pushed page — to show the two things that make `guided_flow` different:
/// the user taps the real buttons, and the spotlight survives the route change.
void main() => runApp(const ExampleApp());

/// Ids shared by the steps and the anchors. Constants, so a typo is a compile
/// error rather than a tour that quietly does nothing.
class Anchors {
  const Anchors._();

  static const String addButton = 'add-button';
  static const String category = 'category';
  static const String amount = 'amount';
  static const String save = 'save';
}

final List<GuidedFlowStep> tourSteps = [
  const GuidedFlowStep(
    anchorId: Anchors.addButton,
    title: 'Start here',
    body: 'Every expense begins with this button. Give it a tap.',
    actionLabel: 'Tap the + button',
    shape: BoxShape.circle,
    padding: EdgeInsets.all(10),
  ),
  const GuidedFlowStep(
    anchorId: Anchors.category,
    title: 'Pick a category',
    body: 'The sheet slides up and the spotlight follows it — no extra work.',
    actionLabel: 'Tap Groceries',
  ),
  const GuidedFlowStep(
    anchorId: Anchors.amount,
    title: 'Check the amount',
    body:
        'Nothing to press here, so this step waits for the button on the card. '
        'Use waitForButton whenever two steps share one screen.',
    actionLabel: 'Looks right',
    waitForButton: true,
  ),
  const GuidedFlowStep(
    anchorId: Anchors.save,
    title: 'Save it',
    body: 'Tapping this really saves — the tour never stood in the way.',
    actionLabel: 'Tap Save',
    endsTour: true,
  ),
];

class ExampleApp extends StatefulWidget {
  const ExampleApp({super.key});

  @override
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  late final GuidedFlowController _tour = GuidedFlowController(
    steps: tourSteps,
    onFinish: (ending) => debugPrint('Tour ended: ${ending.name}'),
  );

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'guided_flow example',
      theme: ThemeData(
        colorSchemeSeed: const Color(0xFF4F46E5),
        useMaterial3: true,
      ),
      // One line, above the Navigator: that is the whole installation.
      builder: (context, child) => GuidedFlow(
        controller: _tour,
        child: child!,
      ),
      home: HomePage(tour: _tour),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key, required this.tour});

  final GuidedFlowController tour;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Expenses')),
      body: Center(
        child: FilledButton.tonal(
          onPressed: tour.start,
          child: const Text('Start the tour'),
        ),
      ),
      floatingActionButton: GuidedFlowAnchor(
        id: Anchors.addButton,
        child: FloatingActionButton(
          onPressed: () => _openCategorySheet(context),
          child: const Icon(Icons.add),
        ),
      ),
    );
  }

  Future<void> _openCategorySheet(BuildContext context) async {
    final String? category = await showModalBottomSheet<String>(
      context: context,
      builder: (_) => const CategorySheet(),
    );
    if (category == null || !context.mounted) return;
    await Navigator.of(context).push(
      MaterialPageRoute<void>(builder: (_) => ExpensePage(category: category)),
    );
  }
}

class CategorySheet extends StatelessWidget {
  const CategorySheet({super.key});

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const SizedBox(height: 12),
          const Text('What did you spend on?'),
          const SizedBox(height: 12),
          GuidedFlowAnchor(
            id: Anchors.category,
            child: ListTile(
              leading: const Icon(Icons.shopping_basket_outlined),
              title: const Text('Groceries'),
              onTap: () => Navigator.of(context).pop('Groceries'),
            ),
          ),
          ListTile(
            leading: const Icon(Icons.directions_bus_outlined),
            title: const Text('Transport'),
            onTap: () => Navigator.of(context).pop('Transport'),
          ),
          const SizedBox(height: 12),
        ],
      ),
    );
  }
}

class ExpensePage extends StatelessWidget {
  const ExpensePage({super.key, required this.category});

  final String category;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(category)),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            GuidedFlowAnchor(
              id: Anchors.amount,
              child: Card(
                child: ListTile(
                  title: const Text('Amount'),
                  subtitle: const Text('Rp 250.000'),
                  trailing: const Icon(Icons.edit_outlined),
                  onTap: () {},
                ),
              ),
            ),
            const Spacer(),
            GuidedFlowAnchor(
              id: Anchors.save,
              child: FilledButton(
                onPressed: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Saved.')),
                  );
                  Navigator.of(context).pop();
                },
                child: const Text('Save'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
2
likes
155
points
93
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Interactive guided tours for Flutter. Users tap the real widgets: spotlights are tap-through, work across bottom sheets and pushed routes, and advance automatically.

Repository (GitHub)
View/report issues

Topics

#onboarding #tutorial #ui #widget

License

MIT (license)

Dependencies

flutter

More

Packages that depend on guided_flow