immutable_fsm 1.1.0 copy "immutable_fsm: ^1.1.0" to clipboard
immutable_fsm: ^1.1.0 copied to clipboard

An immutable state machine that supports transient states, integrates with various state and data management frameworks, and is ideal for UI applications.

example/immutable_fsm_example.dart

/// An example of usage of [ImmutableFSM] based on a modified example
/// of a [coin-operated turnstile](https://en.wikipedia.org/wiki/Finite-state_machine#Example:_coin-operated_turnstile).
library;

import 'dart:convert';
import 'dart:io';

import 'package:immutable_fsm/immutable_fsm.dart';
import 'package:meta/meta.dart';

void main() async {
  /// The configured turnstile state machine.
  ImmutableFSM<TurnstileEvent, TurnstileMetadata?> fsm = _initialFSM;

  String? input;
  while (input != 'q') {
    print('\nTurnstile is: ${fsm.state.runtimeType}');
    final Object? error = fsm.data?.error;
    if (error != null) {
      print('Error: $error');
    }
    print('Coin: ${fsm.data?.coinValue}');
    print('''Enter the following:
    - an integer - insert coin of that value
    - p - push the turnstile
    - hit Enter - prints FSM state and configuration  
    - q - Quit
    ''');
    stdout.write('> ');
    input = stdin.readLineSync(encoding: utf8);
    final int? value = int.tryParse(input ?? '');
    try {
      switch (input) {
        case '':
          print('\n${fsm.debugDescription}\n');
        case 'p':
          // Try to push the turnstile
          fsm = await fsm.tryTransition(event: TurnstileEvent.push);
        case 'q':
          break;
        default:
          if (value != null) {
            // Try to put in the coin
            fsm = await fsm.tryTransition(
              event: TurnstileEvent.coinInserted,
              data: TurnstileMetadata(coinValue: value),
            );
            break;
          }

          print('Command not recognized');
      }
    } on Exception catch (exception) {
      print('Unable to process input: $exception');
    }
  }
}

/// Turnstile events that state machine understands.
enum TurnstileEvent {
  /// A coin is put into the turnstile.
  coinInserted,

  /// Unlock the turnstile to allow the passage.
  unlock,

  /// Push the turnstile to walk through.
  push,

  /// Turnstile reported an error.
  error,
}

/// The data associated with turnstile.
///
/// It covers both input and output of the states, keeping the inserted
/// [coinValue] and [error], if any.
@immutable
class TurnstileMetadata {
  const TurnstileMetadata({this.coinValue = 0, this.error});

  final int coinValue;
  final Object? error;

  @override
  String toString() =>
      'TurnstileMetadata{coinValue: $coinValue, error: $error}';
}

/// Locked turnstile - passage is blocked.
class Locked extends FSMState<TurnstileEvent, TurnstileMetadata> {
  const Locked();
}

/// An intermediate state when turnstile processes the coin.
class ReceivingCoin extends FSMState<TurnstileEvent, TurnstileMetadata> {
  const ReceivingCoin();

  /// When a coin is inserted, it verifies that there is a coin and that it's
  /// a coin of the correct value.
  ///
  /// If everything matches, it emits [TurnstileEvent.unlock] and clears the
  /// coin from [TurnstileMetadata] to represent that it went through.
  @override
  Future<void> onEnter(
    TurnstileMetadata? data, {
    required FSMStateOnEnterResponse<TurnstileEvent, TurnstileMetadata>
        response,
  }) async {
    final int? coinValue = data?.coinValue;
    if (coinValue == null) {
      response
        ..emitEvent(TurnstileEvent.error)
        ..emitData(
          const TurnstileMetadata(
            error: WrongCoinException(
              'You have to put in a real coin.',
            ),
          ),
        );
      return;
    }
    if (coinValue == 50) {
      response
        ..emitEvent(TurnstileEvent.unlock)
        ..emitData(const TurnstileMetadata());
      return;
    }
    response
      ..emitEvent(TurnstileEvent.error)
      ..emitData(
        TurnstileMetadata(
          coinValue: coinValue,
          error: WrongCoinException(
            'A coin of 50 is required, but $coinValue was provided - returned.',
          ),
        ),
      );
  }
}

/// Unlocked turnstile, allowing to walk through.
class Unlocked extends FSMState<TurnstileEvent, TurnstileMetadata> {
  const Unlocked();
}

/// An error - representing a state when turnstile refused to accept a coin.
class CoinError extends FSMState<TurnstileEvent, TurnstileMetadata> {
  const CoinError();
}

/// An exception, representing an incorrect coin.
class WrongCoinException implements Exception {
  const WrongCoinException(this.message);

  final String message;

  @override
  String toString() => 'WrongCoinException: $message';
}

/// The configuration of the state machine, which is also it's initial state.
final ImmutableFSM<TurnstileEvent, TurnstileMetadata> _initialFSM =
    const ImmutableFSM<TurnstileEvent, TurnstileMetadata>(
  initialState: Locked(),
  data: TurnstileMetadata(),
)
        .addTransition(
          from: const Locked(),
          to: const ReceivingCoin(),
          event: TurnstileEvent.coinInserted,
        )
        .addTransition(
          from: const ReceivingCoin(),
          to: const Unlocked(),
          event: TurnstileEvent.unlock,
        )
        .addTransition(
          from: const ReceivingCoin(),
          to: const CoinError(),
          event: TurnstileEvent.error,
        )
        .addTransition(
          from: const Unlocked(),
          to: const Locked(),
          event: TurnstileEvent.push,
        )
        .addTransition(
          from: const CoinError(),
          to: const ReceivingCoin(),
          event: TurnstileEvent.coinInserted,
        );
0
likes
160
pub points
0%
popularity

Publisher

unverified uploader

An immutable state machine that supports transient states, integrates with various state and data management frameworks, and is ideal for UI applications.

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

collection, fast_immutable_collections, meta

More

Packages that depend on immutable_fsm