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

A Flutter library for building declarative, step-by-step flows with comprehensive state management and customizable navigation behavior.

example/example.dart

library;
/// Payment Flow Example for Sequential Flow
///
/// This example demonstrates how to create a complete payment processing flow
/// using the Sequential Flow library. It showcases:
/// - Multi-step flow with different UI states
/// - User input collection and validation
/// - Error handling with retry functionality
/// - Custom back navigation behavior
/// - Data persistence between steps
///
/// The payment flow consists of 5 steps:
/// 1. Amount selection
/// 2. Payment method selection
/// 3. Payment details entry
/// 4. Payment processing
/// 5. Confirmation screen

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

/// Enum defining the different steps in the payment flow.
///
/// Each step represents a distinct phase of the payment process,
/// from initial amount selection to final confirmation.
enum PaymentStep {
  /// User selects the payment amount from predefined options
  selectAmount,

  /// User chooses between credit card or bank transfer
  selectMethod,

  /// User enters payment details (card info or bank details)
  enterDetails,

  /// System processes the payment (with simulated success/failure)
  processing,

  /// Final confirmation screen showing payment success
  confirmation,
}

/// Main payment flow widget that orchestrates the entire payment process.
///
/// This widget demonstrates a real-world use case of the Sequential Flow library,
/// showing how to handle user input, data validation, error recovery, and
/// different navigation behaviors throughout a multi-step process.
///
/// Example usage:
/// ```dart
/// Navigator.of(context).push(
///   MaterialPageRoute(
///     builder: (context) => const PaymentFlowWidget(),
///   ),
/// );
/// ```
class PaymentFlowWidget extends StatelessWidget {
  const PaymentFlowWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Payment Process'),
        backgroundColor: Colors.blue,
      ),
      body: SequentialFlow<PaymentStep>(
        steps: [
          // Step 1: Amount Selection
          // Users choose from predefined amounts with confirmation dialog
          FlowStep<PaymentStep>(
            step: PaymentStep.selectAmount,
            name: 'Select Amount',
            progressValue: 0.2,
            onStepCallback: () async {
              // Simulate amount selection processing
              await Future.delayed(const Duration(seconds: 1));
            },
            requiresConfirmation: (controller) => AlertDialog(
              title: const Text('Select Amount'),
              content: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  ListTile(
                    title: const Text('\$10.00'),
                    onTap: () {
                      controller.setData('amount', 10.00);
                      controller.continueFlow();
                    },
                  ),
                  ListTile(
                    title: const Text('\$25.00'),
                    onTap: () {
                      controller.setData('amount', 25.00);
                      controller.continueFlow();
                    },
                  ),
                  ListTile(
                    title: const Text('\$50.00'),
                    onTap: () {
                      controller.setData('amount', 50.00);
                      controller.continueFlow();
                    },
                  ),
                ],
              ),
            ),
            // Show cancel dialog if user tries to go back
            actionOnPressBack: ActionOnPressBack.showCancelDialog,
          ),

          // Step 2: Payment Method Selection
          // Users choose between credit card and bank transfer
          FlowStep<PaymentStep>(
            step: PaymentStep.selectMethod,
            name: 'Payment Method',
            progressValue: 0.4,
            onStepCallback: () async {
              await Future.delayed(const Duration(milliseconds: 500));
            },
            requiresConfirmation: (controller) => AlertDialog(
              title: const Text('Select Payment Method'),
              content: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  ListTile(
                    leading: const Icon(Icons.credit_card),
                    title: const Text('Credit Card'),
                    onTap: () {
                      controller.setData('method', 'card');
                      controller.continueFlow();
                    },
                  ),
                  ListTile(
                    leading: const Icon(Icons.account_balance),
                    title: const Text('Bank Transfer'),
                    onTap: () {
                      controller.setData('method', 'bank');
                      controller.continueFlow();
                    },
                  ),
                ],
              ),
            ),
            // Allow going back to previous step
            actionOnPressBack: ActionOnPressBack.goToPreviousStep,
          ),

          // Step 3: Payment Details Entry
          // Dynamic form based on selected payment method
          FlowStep<PaymentStep>(
            step: PaymentStep.enterDetails,
            name: 'Enter Details',
            progressValue: 0.6,
            onStepCallback: () async {
              await Future.delayed(const Duration(milliseconds: 800));
            },
            requiresConfirmation: (controller) {
              final method = controller.getData('method');
              return AlertDialog(
                title: Text('Enter ${method == 'card' ? 'Card' : 'Bank'} Details'),
                content: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    // Show different fields based on payment method
                    if (method == 'card') ...[
                      TextField(
                        decoration: const InputDecoration(
                          labelText: 'Card Number',
                          hintText: '1234 5678 9012 3456',
                        ),
                        onChanged: (value) => controller.setData('cardNumber', value),
                      ),
                      const SizedBox(height: 10),
                      TextField(
                        decoration: const InputDecoration(
                          labelText: 'CVV',
                          hintText: '123',
                        ),
                        onChanged: (value) => controller.setData('cvv', value),
                      ),
                    ] else ...[
                      TextField(
                        decoration: const InputDecoration(
                          labelText: 'Account Number',
                          hintText: '1234567890',
                        ),
                        onChanged: (value) => controller.setData('accountNumber', value),
                      ),
                      const SizedBox(height: 10),
                      TextField(
                        decoration: const InputDecoration(
                          labelText: 'Routing Number',
                          hintText: '987654321',
                        ),
                        onChanged: (value) => controller.setData('routingNumber', value),
                      ),
                    ],
                  ],
                ),
                actions: [
                  TextButton(
                    onPressed: () => controller.continueFlow(),
                    child: const Text('Continue'),
                  ),
                ],
              );
            },
            actionOnPressBack: ActionOnPressBack.goToPreviousStep,
          ),

          // Step 4: Payment Processing
          // Simulates actual payment processing with potential failure
          FlowStep<PaymentStep>(
            step: PaymentStep.processing,
            name: 'Processing Payment',
            progressValue: 0.8,
            onStepCallback: () async {
              // Simulate payment processing time
              await Future.delayed(const Duration(seconds: 3));

              // Simulate random failure (20% chance)
              // This demonstrates error handling in the flow
              if (DateTime.now().millisecond % 5 == 0) {
                throw Exception('Payment failed. Please try again.');
              }
            },
            // Block navigation during payment processing for security
            actionOnPressBack: ActionOnPressBack.block,
          ),

          // Step 5: Payment Confirmation
          // Final step showing successful payment completion
          FlowStep<PaymentStep>(
            step: PaymentStep.confirmation,
            name: 'Payment Confirmation',
            progressValue: 1.0,
            onStepCallback: () async {
              await Future.delayed(const Duration(milliseconds: 500));
            },
            // Allow exiting after successful payment
            actionOnPressBack: ActionOnPressBack.saveAndExit,
          ),
        ],

        /// Custom loading widget displayed during step execution.
        ///
        /// Shows progress indicator, step name, and completion percentage.
        /// Includes special messaging for the payment processing step.
        onStepLoading: (step, name, progress) => Padding(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CircularProgressIndicator(
                value: progress,
                strokeWidth: 6,
              ),
              const SizedBox(height: 24),
              Text(
                name,
                style: const TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 8),
              Text(
                '${(progress * 100).toInt()}% Complete',
                style: const TextStyle(
                  fontSize: 16,
                  color: Colors.grey,
                ),
              ),
              // Special message during payment processing
              if (step == PaymentStep.processing) ...[
                const SizedBox(height: 16),
                const Text(
                  'Please wait while we process your payment...',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 14),
                ),
              ],
            ],
          ),
        ),

        /// Success screen displayed when the flow completes successfully.
        ///
        /// Shows payment confirmation with details from the flow data,
        /// demonstrating how data persists throughout the entire process.
        onStepFinish: (step, name, progress, controller) => Padding(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(
                Icons.check_circle,
                size: 80,
                color: Colors.green,
              ),
              const SizedBox(height: 24),
              const Text(
                'Payment Successful!',
                style: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                  color: Colors.green,
                ),
              ),
              const SizedBox(height: 16),
              // Display payment details collected during the flow
              Text(
                'Amount: \$${controller.getData('amount')?.toStringAsFixed(2)}',
                style: const TextStyle(fontSize: 18),
              ),
              Text(
                'Method: ${controller.getData('method') == 'card' ? 'Credit Card' : 'Bank Transfer'}',
                style: const TextStyle(fontSize: 16, color: Colors.grey),
              ),
              const SizedBox(height: 32),
              ElevatedButton(
                onPressed: () => Navigator.of(context).pop(),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.green,
                  padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
                ),
                child: const Text(
                  'Done',
                  style: TextStyle(fontSize: 16),
                ),
              ),
            ],
          ),
        ),

        /// Error screen with retry functionality.
        ///
        /// Displays when a step fails (e.g., payment processing failure).
        /// Provides options to retry the failed step or cancel the entire flow.
        onStepError: (step, name, error, stack, controller) => Padding(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(
                Icons.error_outline,
                size: 80,
                color: Colors.red,
              ),
              const SizedBox(height: 24),
              Text(
                'Error in $name',
                style: const TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                  color: Colors.red,
                ),
              ),
              const SizedBox(height: 16),
              Text(
                error.toString(),
                textAlign: TextAlign.center,
                style: const TextStyle(fontSize: 16),
              ),
              const SizedBox(height: 32),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    onPressed: () => controller.retry(),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.blue,
                    ),
                    child: const Text('Retry'),
                  ),
                  OutlinedButton(
                    onPressed: () => Navigator.of(context).pop(),
                    child: const Text('Cancel'),
                  ),
                ],
              ),
            ],
          ),
        ),

        /// Custom back button handling.
        ///
        /// Provides a custom cancel dialog for the first step,
        /// demonstrating how to implement custom navigation behavior.
        onPressBack: (controller) {
          final currentStep = controller.steps[controller.currentStepIndex];

          // Handle cancel dialog for the first step
          if (currentStep.actionOnPressBack == ActionOnPressBack.showCancelDialog) {
            return AlertDialog(
              title: const Text('Cancel Payment?'),
              content: const Text('Are you sure you want to cancel this payment?'),
              actions: [
                TextButton(
                  onPressed: () => Navigator.of(context).pop(),
                  child: const Text('Continue'),
                ),
                TextButton(
                  onPressed: () {
                    controller.cancelFlow();
                  },
                  style: TextButton.styleFrom(foregroundColor: Colors.red),
                  child: const Text('Cancel Payment'),
                ),
              ],
            );
          }

          return const SizedBox.shrink();
        },
      ),
    );
  }
}

/// Demo widget to launch the payment flow.
///
/// This widget provides a simple entry point to test the payment flow.
/// In a real application, this would typically be integrated into
/// a shopping cart, subscription signup, or other payment context.
///
/// Example usage in your main app:
/// ```dart
/// class MyApp extends StatelessWidget {
///   @override
///   Widget build(BuildContext context) {
///     return MaterialApp(
///       home: PaymentFlowDemo(),
///     );
///   }
/// }
/// ```
class PaymentFlowDemo extends StatelessWidget {
  const PaymentFlowDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sequential Flow Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.payment,
              size: 80,
              color: Colors.blue,
            ),
            const SizedBox(height: 24),
            const Text(
              'Payment Flow Example',
              style: TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 32),
              child: Text(
                'Demonstrates a complete payment process using Sequential Flow library',
                textAlign: TextAlign.center,
                style: TextStyle(
                  fontSize: 16,
                  color: Colors.grey,
                ),
              ),
            ),
            const SizedBox(height: 32),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) => const PaymentFlowWidget(),
                  ),
                );
              },
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.blue,
                padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
              ),
              child: const Text(
                'Start Payment Flow',
                style: TextStyle(fontSize: 16),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
150
points
9
downloads

Publisher

verified publisherjhonacode.com

Weekly Downloads

A Flutter library for building declarative, step-by-step flows with comprehensive state management and customizable navigation behavior.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on sequential_flow