Flutter情感表达插件love的使用

Flutter情感表达插件love的使用

Love

Why

love 具有 ReactiveXReduxRxFeedback 的基因。因此它:

  • 统一 - 一个就是一切,一切就是一个 (System<State, Event>)
  • 声明式 - 系统首先声明,在调用 run 后开始执行
  • 可预测 - 单向数据流
  • 灵活 - 随着复杂应用可以很好地扩展
  • 优雅 - 代码对人类来说易于阅读和编写
  • 可测试 - 系统可以轻松进行测试

目录

  • love - 仅 Dart 的状态管理库
  • flutter_love - 提供处理常见场景的 Flutter 小部件
  • flutter_love_provider - 提供基于 love 和 provider 的解决方案的 Flutter 小部件

计数器示例

// typedef CounterState = int;

abstract class CounterEvent {}
class Increment implements CounterEvent {}
class Decrement implements CounterEvent {}

void main() async {

  final counterSystem = System&lt;int, CounterEvent&gt;
    .create(initialState: 0)
    .on&lt;Increment&gt;(
      reduce: (state, event) =&gt; state + 1,
      effect: (state, event, dispatch) async {
        await Future&lt;void&gt;.delayed(const Duration(seconds: 3));
        dispatch(Decrement());
      },
    )
    .on&lt;Decrement&gt;(
      reduce: (state, event) =&gt; state - 1,
    )
    .log()
    .reactState(
      effect: (state, dispatch) {
        print('Simulate persistence save call with state: $state');
      },
    )
    .onRun(effect: (initialState, dispatch) {
      dispatch(Increment());
      return null;
    },);

  final disposer = counterSystem.run();

  await Future&lt;void&gt;.delayed(const Duration(seconds: 6));

  disposer();
}

输出:

System&lt;int, CounterEvent&gt; Run
System&lt;int, CounterEvent&gt; Update {
  event: null
  oldState: null
  state: 0
}
System&lt;int, CounterEvent&gt; Update {
  event: Instance of 'Increment'
  oldState: 0
  state: 1
}
System&lt;int, CounterEvent&gt; Update {
  event: Instance of 'Decrement'
  oldState: 1
  state: 0
}
System&lt;int, CounterEvent&gt; Dispose

我们希望这段代码能够自解释。如果你能猜出这段代码的作用,那就太好了!

此示例首先声明了一个计数器系统,状态是计数,事件是 incrementdecrement。然后我们运行系统来记录输出,6 秒后停止系统。

当前代码并不十分优雅,我们有更好的方法来实现相同的功能。当我们掌握新技能时,我们将逐步重构代码。我们保持这种写法,因为它是一个很好的起点,用于演示如何工作。

核心

它是如何工作的?

状态

状态是某个时刻的数据快照。

例如,计数器的状态是计数:

// typedef CounterState = int;

事件

事件是对已发生事情的描述。

例如,计数器的事件是 incrementdecrement,它们描述了发生了什么:

abstract class CounterEvent {}
class Increment implements CounterEvent {}
class Decrement implements CounterEvent {}

减少

减少是一个函数,描述了当事件发生时状态如何更新。

typedef Reduce&lt;State, Event&gt; = State Function(State state, Event event);

计数器示例:

    ...
    .on&lt;Increment&gt;(
      reduce: (state, event) =&gt; state + 1,
    )
    .on&lt;Decrement&gt;(
      reduce: (state, event) =&gt; state - 1,
    )
    ...

如果发生 increment 事件,我们增加计数;如果发生 decrement 事件,我们减少计数。

我们可以使它更简洁:

    ...
-   .on&lt;Increment&gt;(
-     reduce: (state, event) =&gt; state + 1,
-   )
-   .on&lt;Decrement&gt;(
-     reduce: (state, event) =&gt; state - 1,
-   )
+   .on&lt;Increment&gt;(
+     reduce: (state, event) =&gt; state + 1,
+   )
+   .on&lt;Decrement&gt;(
+     reduce: (state, event) =&gt; state - 1,
+   )
    ...

这对我们来说更优雅地读取和编写代码。

注意:减少是一个纯函数,其唯一目的是用当前状态和事件计算出新的状态。此函数中没有副作用。

那么如何处理副作用呢?

效果

效果是一个导致外部可观察效果的函数。

typedef Effect&lt;State, Event&gt; = void Function(State state, State? oldState, Event? event, Dispatch&lt;Event&gt; dispatch);

副作用:

  • 呈现
  • 日志
  • 网络
  • 持久化
  • 分析
  • 蓝牙
  • 定时器

以下是 日志效果模拟效果

    ...
    .log()
    .reactState(
      effect: (state, dispatch) {
        print('Simulate persistence save call with state: $state');
      },
    )
    ...

那么异步的东西,比如 网络效果定时器效果 如何处理:

    ...
    .log()
    .reactState(
      effect: (state, dispatch) {
        print('Simulate persistence save call with state: $state');
      },
    )
+   .on&lt;Increment&gt;(
+     effect: (state, event, dispatch) async {
+       await Future&lt;void&gt;.delayed(const Duration(seconds: 3));
+       dispatch(Decrement());
+     },
+   )
    ...

我们添加了一个 定时器效果,当发生 increment 事件时,我们将在 3 秒后分发一个 decrement 事件以恢复计数。

我们还可以添加 持久化效果

    ...
    .on&lt;Increment&gt;(
      effect: (state, event, dispatch) async {
        await Future&lt;void&gt;.delayed(const Duration(seconds: 3));
        dispatch(Decrement());
      },
    )
+   .reactState(
+     effect: (state, dispatch) {
+       if (event != null &amp;&amp; oldState != state) {
+         print('Simulate persistence save call with state: $state');
+       }
+     },
+   ),
    ...

此持久化保存功能将在状态更改时被调用,但初始状态将被跳过,因为大多数情况下初始状态是从持久层恢复的,无需再次保存。

运行

我们已经声明了我们的 counterSystem

final counterSystem = System&lt;int, CounterEvent&gt;
  ...;

在调用 run 之前,它不会做任何事情:

final disposer = counterSystem.run();

当调用 run 时,会返回一个 disposer。稍后我们可以使用这个 disposer 来停止系统:

// 在 6 秒后停止系统

await Future&lt;void&gt;.delayed(const Duration(seconds: 6)); 

disposer();

效果细节

由于效果在这里扮演着重要角色,让我们深入研究一下。

效果触发

我们添加了 定时器效果持久化效果。现在,与其思考这是一个什么样的效果,不如关注这些效果是如何 触发 的:

    ...
    .on&lt;Increment&gt;(
      effect: (state, event, dispatch) async {
        await Future&lt;void&gt;.delayed(const Duration(seconds: 3));
        dispatch(Decrement());
      },
    )
    .reactState(
      effect: (state, dispatch) {
        print('Simulate persistence save call with state: $state');
      },
    ),
    ...

不难发现第一个 定时器效果 是在 increment 事件发生时触发的,第二个 持久化效果 是通过反应到状态变化而触发的。

这里,我们有两种 效果触发

  • 事件触发
  • 状态触发

事件触发

事件触发会在事件满足某些条件时触发效果。

我们有一系列前缀为 on 的操作符(方法)来更好地实现这一点:

    ...
-   .on&lt;Increment&gt;(
-     effect: (state, event, dispatch) async {
-       await Future&lt;void&gt;.delayed(const Duration(seconds: 3));
-       dispatch(Decrement());
-     },
-   )
+   .on&lt;Increment&gt;(
+     effect: (state, event, dispatch) async {
+       await Future&lt;void&gt;.delayed(const Duration(seconds: 3));
+       dispatch(Decrement());
+     },
+   )
    ...

我们甚至可以在共享相同条件的情况下将 effect 移动到 reduce 旁边:

    ...
    .on&lt;Increment&gt;(
      reduce: (state, event) =&gt; state + 1,
+     effect: (state, event, dispatch) async {
+       await Future&lt;void&gt;.delayed(const Duration(seconds: 3));
+       dispatch(Decrement());
+     },
    )
    .on&lt;Decrement&gt;(
      reduce: (state, event) =&gt; state - 1,
    )
    ...
-   .on&lt;Increment&gt;(
-     effect: (state, event, dispatch) async {
-       await Future&lt;void&gt;.delayed(const Duration(seconds: 3));
-       dispatch(Decrement());
-     },
-   )
    ...

有一些特殊情况。例如,我们想在系统启动时分发事件:

    ...
    .onRun(effect: (initialState, dispatch) {
      dispatch(Increment());
      return null;
    },);

我们还可以使用 onRun 操作符来处理不同的情况。了解更多请参阅 API 文档

  • on
  • onRun
  • onDispose

状态触发

状态触发会通过反应到状态变化来触发效果。

我们有一系列前缀为 react 的操作符来实现这一点:

    ...
-   .reactState(
-     effect: (state, dispatch) {
-       print('Simulate persistence save call with state: $state');
-     },
-   ),
+   .reactState(
+     effect: (state, dispatch) {
+       print('Simulate persistence save call with state: $state');
+     },
+   ),
    ...

此效果将在状态变化时反应并触发保存调用。由于它对整个状态(而非部分值)的变化作出反应,我们可以使用方便的操作符 reactState,这样就不需要值映射函数:

-   .reactState(
-     effect: (state, dispatch) {
-       print('Simulate persistence save call with state: $state');
-     },
-   ),
+   .reactState(
+     effect: (state, dispatch) {
+       print('Simulate persistence save call with state: $state');
+     },
+   ),

还有一个重要的效果使用了这种触发方式。你能猜出是什么吗?

提示: FlutterReact

是的,这是 呈现效果。使用声明式 UI 库如 FlutterReact 时,构建(渲染)是由反应到状态变化触发的。 我们稍后将讨论这个问题。

还有其他 react* 操作符用于不同情况。了解更多请访问 API 文档

  • react
  • reactLatest
  • reactState

呈现效果(与 Flutter)

我们提到 呈现效果 是由反应到状态变化触发的:

  .reactState(
    effect: (state, dispatch) {
      print('Simulate presentation effect (build, render) with state: $state');
    },
  )

由于 Flutter 充满了小部件,我们如何让 react* 操作符 与小部件一起工作?

这是可能的吗:

  // 下面只是想象,只在我们心中工作
  .reactState(
    effect: (state, dispatch) {
      return TextButton(
        onPressed: () =&gt; dispatch(Increment()),
        child: Text('$state'),
      );
    },
  )

是的,我们可以引入 <React*> 小部件,它们是 <react*> 操作符 和小部件的组合:

Widget build(BuildContext context) {
  return ReactState&lt;int, CounterEvent&gt;(
    system: counterSystem,
    builder: (context, state, dispatch) {
      return TextButton(
        onPressed: () =&gt; dispatch(Increment()),
        child: Text('$state'),
      );
    }
  );
}

很高兴看到 FlutterReact 的合作 ^_^。

了解更多请访问 flutter_love

日志效果

我们介绍了如何添加 日志效果

    ...
    .log()
    ...

输出:

System&lt;int, CounterEvent&gt; Run
System&lt;int, CounterEvent&gt; Update {
  event: null
  oldState: null
  state: 0
}
System&lt;int, CounterEvent&gt; Update {
  event: Instance of 'Increment'
  oldState: 0
  state: 1
}
System&lt;int, CounterEvent&gt; Update {
  event: Instance of 'Decrement'
  oldState: 1
  state: 0
}
System&lt;int, CounterEvent&gt; Dispose

我们看到,日志 操作符可以用更少的代码完成更多的工作,它不仅记录 更新,还记录系统 运行销毁,这对于调试可能很有帮助。

日志 是一种 场景聚焦操作符,它可以随着详细解决方案的需求扩展日志。如果我们反复编写类似的代码来解决类似的问题,那么我们可以提取操作符以便重用解决方案。日志 就是这样的一个操作符之一。

其他操作符

还有一些操作符可以帮助我们实现目标。我们将介绍其中一些。

ignoreEvent

根据当前状态和候选事件忽略事件。

  futureSystem
    .ignoreEvent(
      when: (state, event) =&gt; event is TriggerLoadData &amp;&amp; state.loading
    )
    ...

上述代码表明,如果系统已经在加载状态,则接下来的 TriggerLoadData 事件将被忽略。

debounceOn

对某些事件应用 去抖逻辑

  searchSystem
    ...
    .on&lt;UpdateKeyword&gt;(
      reduce: (state, event) =&gt; state.copyWith(keyword: event.keyword)
    )
    .debounceOn&lt;UpdateKeyword&gt;(
      duration: const Duration(seconds: 1)
    )
    ...

上述代码表明,如果 UpdateKeyword 事件以高频(快速输入)分发,系统将丢弃这些事件以减少不必要的分发,只有当分发事件稳定时才会传递事件。

附录

代码审查

我们已经重构了很多代码。让我们回顾一下,以增强肌肉记忆。

旧代码:

final counterSystem = System&lt;int, CounterEvent&gt
  .create(initialState: 0)
  .on&lt;Increment&gt;(
    reduce: (state, event) =&gt; state + 1,
    effect: (state, event, dispatch) async {
      await Future&lt;void&gt;.delayed(const Duration(seconds: 3));
      dispatch(Decrement());
    },
  )
  .on&lt;Decrement&gt;(
    reduce: (state, event) =&gt; state - 1,
  )
  .log()
  .reactState(
    effect: (state, dispatch) {
      print('Simulate persistence save call with state: $state');
    },
  )
  .onRun(effect: (initialState, dispatch) {
    dispatch(Increment());
    return null;
  },);

新代码:

final counterSystem = System&lt;int, CounterEvent&gt
  .create(initialState: 0)
  .on&lt;Increment&gt;(
    reduce: (state, event) =&gt; state + 1,
    effect: (state, event, dispatch) async {
      await Future&lt;void&gt;.delayed(const Duration(seconds: 3));
      dispatch(Decrement());
    },
  )
  .on&lt;Decrement&gt;(
    reduce: (state, event) =&gt; state - 1,
  )
  .log()
  .reactState(
    effect: (state, dispatch) {
      print('Simulate persistence save call with state: $state');
    },
  )
  .onRun(effect: (initialState, dispatch) {
    dispatch(Increment());
    return null;
  },);

测试

测试可以简单地进行:

  1. 创建系统
  2. 注入模拟事件和模拟效果
  3. 记录状态
  4. 运行系统
  5. 验证记录的状态
test('CounterSystem', () async {

  final List&lt;State&gt; states = [];

  final counterSystem = System&lt;int, CounterEvent&gt
    .create(initialState: 0)
    .on&lt;Increment&gt;(
      reduce: (state, event) =&gt; state + 1,
    )
    .on&lt;Decrement&gt;(
      reduce: (state, event) =&gt; state - 1,
    );

  final disposer = counterSystem.run(
    effect: (state, oldState, event, dispatch) async {
      states.add(state);
      if (event == null) {
        // 注入模拟事件
        dispatch(Increment());
        await Future&lt;void&gt;.delayed(const Duration(milliseconds: 20));
        dispatch(Decrement());
      }
    },
  );

  await Future&lt;void&gt;.delayed(const Duration(milliseconds: 60));

  disposer();

  expect(states, [
    0, // 初始状态
    1,
    0,
  ]);
  
});

更多关于Flutter情感表达插件love的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter情感表达插件love的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中集成和使用love插件来实现情感表达功能的一个简单代码案例。love插件可能不是一个实际存在的官方插件名称,但假设它提供了一些类似心形动画或情感图标的功能,我将基于这个假设来展示代码。

首先,你需要确保你的Flutter项目已经设置好,并且已经添加了love插件(或类似的情感表达插件)到你的pubspec.yaml文件中。由于love插件可能不存在,我将使用一个假设的插件名称flutter_love_expression作为示例。

1. 添加依赖

在你的pubspec.yaml文件中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_love_expression: ^1.0.0  # 假设版本号

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

2. 导入插件

在你的Dart文件中导入插件:

import 'package:flutter/material.dart';
import 'package:flutter_love_expression/flutter_love_expression.dart'; // 假设的导入路径

3. 使用插件

下面是一个简单的示例,展示如何在你的Flutter应用中使用flutter_love_expression插件来显示一个心形动画或图标:

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Love Expression Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Love Expression Demo'),
        ),
        body: Center(
          child: LoveExpressionButton(),
        ),
      ),
    );
  }
}

class LoveExpressionButton extends StatefulWidget {
  @override
  _LoveExpressionButtonState createState() => _LoveExpressionButtonState();
}

class _LoveExpressionButtonState extends State<LoveExpressionButton> {
  bool isLoved = false;

  void _expressLove() {
    setState(() {
      isLoved = !isLoved;
    });
    // 假设插件有一个showLoveAnimation方法来显示心形动画
    if (isLoved) {
      LoveExpression.showLoveAnimation(); // 假设的方法调用
    } else {
      // 如果有隐藏动画的方法,也可以在这里调用
      // LoveExpression.hideLoveAnimation();
    }
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _expressLove,
      child: Text(isLoved ? 'Loved!' : 'Express Love'),
      style: ElevatedButton.styleFrom(
        primary: isLoved ? Colors.red : Colors.blue,
      ),
    );
  }
}

注意:上面的代码示例是基于假设的flutter_love_expression插件的功能。实际的插件可能有不同的API和用法。你需要查阅该插件的官方文档来了解如何正确使用它。

如果love插件确实存在,并且有不同的API,你需要根据该插件的实际文档来修改上述代码。通常,插件的README文件或官方文档会提供详细的用法示例和API参考。

回到顶部