Flutter有限状态机管理插件dart_fsm的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter有限状态机管理插件dart_fsm的使用

1. 关于有限状态机

有限状态机(Finite State Machine, FSM)是一种数学抽象的行为模型,由有限数量的状态、转换和动作组成。将应用程序的可能状态建模为有限状态机可以带来以下好处:

  • 减少设计阶段的遗漏:确保在设计阶段考虑到所有可能的状态和转换。
  • 弥合设计与实现之间的差距:通过在设计阶段定义有限状态机,使设计和实现更加一致。
  • 消除不必要的空值检查:通过定义状态机中的状态,避免不必要的空值检查。
  • 防止出现意外的不存在状态:确保所有状态都在状态机中定义,防止出现意外的状态。
  • 防止无意的应用状态更改:通过明确的状态转换规则,防止无意中更改应用状态。
  • 简化测试:状态机的确定性使得测试更加容易。

随着应用程序复杂性的增加,使用有限状态机设计应用程序的效果会更加显著。

2. 动机

虽然使用有限状态机进行状态管理非常有用,但使用 switchif 语句实现状态机可能会导致代码难以阅读且容易出错,尤其是在状态转换变得复杂时。此外,不同的开发人员可能会以不同的方式实现状态转换,从而降低代码的可维护性。因此,在使用有限状态机管理状态时,需要清晰地定义状态转换并自动化这些转换。此外,在应用程序中使用状态管理时,有时希望在状态转换期间触发副作用(如 API 调用)。如果没有统一的实现方式,不同开发人员的实现差异会降低代码的可维护性。

dart_fsm 插件提供了一种机制,用于在使用有限状态机管理状态时清晰地定义状态转换,并自动化这些转换。它还定义了副作用的实现方式,消除了开发人员之间的实现差异,提高了代码的可维护性。

3. 功能

  • 描述状态转换图的 DSL:提供了一种特定领域的语言(DSL),用于描述不带副作用的状态转换图。
  • 实现伴随状态转换的副作用:提供了实现副作用的方法。
  • 处理间歇性值场景(如 Streams):提供了在间歇性值场景(如 Streams)中实现有限状态机的方法。
  • 使用有限状态机进行测试:提供了使用有限状态机进行测试的方法。
  • 测试有限状态机本身:提供了测试有限状态机本身的方法。

4. 使用方法

4.1 描述状态转换图

有限状态机通常使用状态转换图来表示。dart_fsm 提供了一个 DSL 来描述状态转换图。考虑以下状态转换图:

stateDiagram-v2
    [*] --> Initial
    Initial --> Loading: Fetch
    Loading --> Success: Succeed
    Loading --> Error: Fail

我们将使用 dart_fsm 描述这个转换图。首先,定义状态和动作。状态和动作都使用密封类(sealed class)来定义,如下所示:

// 定义状态
sealed class SampleState {
  const SampleState();
}

final class SampleStateInitial extends SampleState {
  const SampleStateInitial();
}

final class SampleStateLoading extends SampleState {
  const SampleStateLoading();
}

final class SampleStateSuccess extends SampleState {
  const SampleStateSuccess(this.data);
  
  final Data data;
}

final class SampleStateError extends SampleState {
  const SampleStateError(this.exception);
  
  final Exception exception;
}
// 定义动作
sealed class SampleAction {
  const SampleAction();
}

final class SampleActionFetch extends SampleAction {
  const SampleActionFetch();
}

final class SampleActionSucceed extends SampleAction {
  const SampleActionSucceed(this.data);
  
  final Data data;
}

final class SampleActionFail extends SampleAction {
  const SampleActionFail(this.exception);
  
  final Exception exception;
}

接下来,使用 GraphBuilder 类描述状态转换图。GraphBuilder 提供了 stateon 方法来描述状态转换。on 方法接受一个函数,该函数接收前一个状态和动作,并返回下一个状态。

final stateGraph = GraphBuilder<SampleState, SampleAction>()
  ..state<SampleStateInitial>(
    (b) => b
      ..on<SampleActionFetch>(
        (state, action) => b.transitionTo(const SampleStateLoading()),
      ),
  )
  ..state<SampleStateLoading>(
    (b) => b
      ..on<SampleActionSucceed>(
        (state, action) => b.transitionTo(SampleStateSuccess(action.data)),
      )
      ..on<SampleActionFail>(
        (state, action) => b.transitionTo(SampleStateError(action.exception)),
      ),
  );

描述完状态转换图后,使用 createStateMachine 方法生成有限状态机。

final stateMachine = createStateMachine(
  initialState: const SampleStateInitial(),
  graphBuilder: stateGraph,
);

生成的有限状态机可以通过 dispatch 方法触发状态转换。

stateMachine.dispatch(const SampleActionFetch());

可以通过 state 属性或 stateStream 属性获取有限状态机的当前状态或状态流。

print(stateMachine.state); 
stateMachine.stateStream.listen((state) {
  print(state);
});
4.2 实现副作用

假设我们希望在转换到 Loading 状态时触发一个副作用,例如发起 API 请求。为此,我们可以使用 SideEffectCreatorSideEffect 来定义副作用。SideEffectCreator 是一个用于生成副作用的类,而 SideEffect 表示具体的副作用。有三种类型的副作用:

  • AfterSideEffectCreator:在发出动作后立即执行,但在状态转换之前。
  • BeforeSideEffectCreator:在发出动作并且状态转换发生后执行。
  • FinallySideEffectCreator:在发出动作后执行,无论状态转换是否发生。

通常情况下,AfterSideEffectCreator 是最常用的,适用于生成因状态转换而发生的副作用,如 API 请求或保存数据。

以下是实现 API 请求副作用的示例:

final class SampleSideEffectCreator
    implements AfterSideEffectCreator<SampleState, SampleAction, SampleSideEffect> {
  const SampleSideEffectCreator(this.apiClient);

  final ApiClient apiClient;

  @override
  SampleSideEffect? create(SampleState state, SampleAction action) {
    return switch (action) {
      SampleActionFetch() => SampleSideEffect(apiClient),
      _ => null,
    };
  }
}

final class SampleSideEffect
    implements AfterSideEffect<SampleState, SampleAction> {
  const SampleSideEffect(this.apiClient);

  final ApiClient apiClient;

  @override
  Future<void> execute(StateMachine<SampleState, SampleAction> stateMachine) async {
    try {
      final data = await apiClient.fetchData();
      stateMachine.dispatch(SampleActionSucceed(data));
    } on Exception catch (e) {
      stateMachine.dispatch(SampleActionFail(e));
    }
  }
}

现在,我们将 SampleSideEffectSampleSideEffectCreator 注册到有限状态机中。

final stateMachine = createStateMachine(
  initialState: const SampleStateInitial(),
  graphBuilder: stateGraph,
  sideEffectCreator: SampleSideEffectCreator(apiClient),
);

每次状态转换发生时,SampleSideEffectCreator 会被调用,如果触发转换的动作是 Fetch,则会生成 SampleSideEffect 并发起 API 请求。

4.3 处理间歇性值场景(如 Streams)

当在间歇性值场景(如 Streams)中实现有限状态机时,可以使用 Subscription 类。SubscriptionStateMachine 实例生成时仅被调用一次,并在其生命周期内保持有效。考虑以下状态转换图:

stateDiagram-v2
    [*] --> Initial
    Initial --> Loading: Fetch
    Loading --> Success: Succeed
    Success --> Success: UpdateData
    Loading --> Error: Fail

假设我们希望在每次发出 UpdateData 动作后更新数据。可以实现一个 Subscription,根据 Stream 中的数据发出 SampleActionUpdate 动作。

final class SampleSubscription
    implements Subscription<SampleState, SampleAction> {
  SampleSubscription(this.webSocketClient);

  final WebSocketClient webSocketClient;

  StreamSubscription<Data>? _subscription;

  @override
  void subscribe(StateMachine<SampleState, SampleAction> stateMachine) {
    _subscription = webSocketClient.subscribeData().listen((data) {
      stateMachine.dispatch(SampleActionUpdate(data));
    });
  }

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

然后将 Subscription 注册到 StateMachine 中。

final stateMachine = createStateMachine(
  initialState: const SampleStateInitial(),
  graphBuilder: stateGraph,
  sideEffectCreator: SampleSideEffectCreator(apiClient),
  subscription: SampleSubscription(webSocketClient),
);

这样,有限状态机会接收来自 WebSocketClient 的数据,并根据数据发出 SampleActionUpdate 动作。

5. 完整示例 Demo

以下是一个完整的示例,展示了如何使用 dart_fsm 实现一个简单的有限状态机,包括状态转换、副作用和间歇性值处理。

import 'package:dart_fsm/dart_fsm.dart';

// 定义状态
sealed class SampleState {
  const SampleState();
}

final class SampleStateA extends SampleState {
  const SampleStateA();
}

final class SampleStateB extends SampleState {
  const SampleStateB();
}

// 定义动作
sealed class SampleAction {
  const SampleAction();
}

final class SampleActionA extends SampleAction {
  const SampleActionA();
}

// 定义状态转换图
void main() {
  final stateMachineGraph = GraphBuilder<SampleState, SampleAction>()
    ..state<SampleStateA>(
      (b) => b
        ..on<SampleActionA>(
          (state, action) => b.transitionTo(const SampleStateB()),
        ),
    );

  // 创建有限状态机
  final stateMachine = createStateMachine(
    initialState: const SampleStateA(),
    graphBuilder: stateMachineGraph,
  );

  // 打印初始状态
  print(stateMachine.state); // SampleStateA

  // 触发状态转换
  stateMachine.dispatch(const SampleActionA());

  // 打印转换后的状态
  print(stateMachine.state); // SampleStateB
}

更多关于Flutter有限状态机管理插件dart_fsm的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter有限状态机管理插件dart_fsm的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用dart_fsm(Dart Finite State Machine)插件来管理有限状态机的示例代码。dart_fsm是一个用于在Dart项目中实现状态机管理的库,Flutter项目自然也可以利用它来进行状态管理。

首先,确保你已经在你的pubspec.yaml文件中添加了dart_fsm依赖:

dependencies:
  flutter:
    sdk: flutter
  dart_fsm: ^最新版本号 # 替换为实际的最新版本号

然后,运行flutter pub get来安装依赖。

示例代码

  1. 定义状态(States)和事件(Events)
// states.dart
enum State {
  IDLE,
  LOADING,
  SUCCESS,
  ERROR,
}

// events.dart
enum Event {
  START_LOADING,
  LOAD_SUCCESS,
  LOAD_FAILURE,
}
  1. 创建状态处理逻辑
// fsm_config.dart
import 'package:dart_fsm/dart_fsm.dart';
import './states.dart';
import './events.dart';

class MyFSMConfig extends FSMConfig<State, Event> {
  @override
  Map<State, Map<Event, State>> getTransitions() {
    return {
      State.IDLE: {
        Event.START_LOADING: State.LOADING,
      },
      State.LOADING: {
        Event.LOAD_SUCCESS: State.SUCCESS,
        Event.LOAD_FAILURE: State.ERROR,
      },
      // 可以添加更多的状态和事件转换逻辑
    };
  }

  @override
  void onEntry(State state) {
    print("Entered state: $state");
  }

  @override
  void onExit(State state) {
    print("Exited state: $state");
  }

  @override
  void onEvent(Event event, State? fromState, State toState) {
    print("Event triggered: $event, From: $fromState, To: $toState");
  }
}
  1. 在Flutter Widget中使用FSM
// main.dart
import 'package:flutter/material.dart';
import 'package:dart_fsm/dart_fsm.dart';
import './fsm_config.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final fsm = FSM<State, Event>(MyFSMConfig());

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('FSM Demo'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Current State: ${fsm.currentState}'),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  fsm.triggerEvent(Event.START_LOADING);
                },
                child: Text('Start Loading'),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  fsm.triggerEvent(Event.LOAD_SUCCESS);
                },
                child: Text('Load Success'),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  fsm.triggerEvent(Event.LOAD_FAILURE);
                },
                child: Text('Load Failure'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

解释

  1. 定义状态和事件:在states.dartevents.dart中分别定义了状态和事件枚举。
  2. 创建状态处理逻辑:在fsm_config.dart中,通过继承FSMConfig类并实现其方法,定义了状态转换逻辑、进入状态时的处理、退出状态时的处理以及事件触发时的处理。
  3. 在Flutter Widget中使用FSM:在main.dart中,创建了一个FSM实例,并在UI中展示了当前状态,同时提供了按钮来触发不同的事件。

这个示例展示了如何使用dart_fsm在Flutter项目中管理状态机。你可以根据需要扩展状态和事件,以及添加更多的状态转换逻辑。

回到顶部