Flutter数据序列化与反序列化插件fling_pickle的使用

Flutter数据序列化与反序列化插件fling_pickle的使用

Fling logo

一个轻量级、小体积的Dart库用于序列化和反序列化(SerDes)。使用Pickles,你无需初始化复杂的系统或注解你的类——所有与SerDes相关的功能都显式地在代码中体现且非常直观。你可以将对象序列化为JSON以提高可读性,或者序列化为二进制以优化工件大小。

使用

虽然Pickles通常用于持久化对象状态或结构,但它们也可以被用作重量级的备忘录对象——例如,用于执行撤销/重做操作。相比经典的备忘录模式,它不允许其他类创建或查看备忘录本身,Pickles提供了基本接口。这种决策的代价是,“伪造”的备忘录(由被序列化的类以外的东西创建)可能被当作原版传递给恶意行为者(或在紧张的截止日期下工作的开发人员)。此外,由于Pickles必须作为一个通用的状态记录,它不如真正的备忘录类型安全。在将传统的备忘录模式替换为Pickles之前,请考虑这些成本。

然而,对于非敏感类,Pickles确实提供了一些作为轻量级序列化核心的好处。你可以快速设置一个类,其状态可以存储在文件或数据库中,稍后恢复,而无需任何混乱的初始化或魔法镜像。这意味着更好的性能和小体积。如果你后来决定需要更重的功能,这很容易提取出来——Pickles不需要对类本身进行任何更改或约定。

Pickles支持序列化到二进制和Dart的JSON表示。这意味着你可以无缝地使用许多核心库和插件,如dart:convert或Firestore。详见下面的示例。

示例

最简单的用例涉及一个希望保存自身并稍后恢复的类。我们来处理一个计数器类:

class Counter {
  int count;

  Counter([final int start = 0]) : count = start;

  int countUp() => count++;
}

完全集成

最基本的方法是我们只需要存储当前的count以恢复对象的状态。我们可以根据想要的侵入性选择多种方法。我们从一个完全集成的例子开始:

class Counter implements Pickleable {
  int count;

  Counter([final int start = 0]) : count = start;

  int countUp() => count++;

  [@override](/user/override)
  Pickle toPickle() => PickleBuilder().withInt('count', count).build();

  static Counter fromPickle(final Pickle pickle) => Counter(pickle.readInt('count'));
}

三个主要的区别在于:

  • 我们实现了Pickleable接口。
  • 我们通过生成包含对象状态的Pickles来实现toPickle()
  • 我们通过生成基于Pickles值的Counter来实现fromPickle()

这样我们就有了与Pickles的完全集成,并可以执行以下操作:

void main() {
  var counter = Counter();
  print(counter.countUp()); // 输出: 0

  var pickle = counter.toPickle();
  print(counter.countUp()); // 输出: 1

  counter = Counter.fromPickle(pickle);
  print(counter.countUp()); // 输出: 1

  // 将我们的pickle写入文件。
  // 我们可以根据需要使用同步或异步方法。
  File('myCounter').writeAsBytesSync(Pickler().writeSync(pickle));
  var pickleFromFile = Pickler().readSync(File('myCounter').readAsBytesSync());

  counter = Counter.fromPickle(pickleFromFile);
  print(counter.countUp()); // 输出: 1
}

此外,我们还完全支持嵌套Pickling。例如,一个使用Counter生成版本号的类可以轻松地“pickle”自身及其内部Counter的状态:

class Version implements Pickleable {
  final Counter _counter;

  Version() : _counter = Counter();

  Version._(final this.counter);

  String nextVersion() => 'v${_counter.countUp()}';

  [@override](/user/override)
  Pickle toPickle() => PickleBuilder().withPickleable('counter', _counter).build();

  static Version fromPickle(final Pickle pickle) => Version._(pickle.readPickleable('counter'));
}

序列化和反序列化一个Version与序列化和反序列化一个Counter没有什么不同,包括将其保存到文件或其他任何形式的持久化方式:

void main() {
  var version = Version();
  print(version.nextVersion()); // 输出: v0

  var pickle = version.toPickle();
  print(version.nextVersion()); // 输出: v1

  version = Version.fromPickle(pickle);
  print(version.nextVersion()); // 输出: v1

  // 保存到文件
  File('MyVersion.pickle').writeAsBytes(BinaryPickler().writeSync(pickle));

  // 或保存到数据库如Firestore
  FirebaseFirestore.instance.collection('versions').add(JsonPickler.writeSync(pickle));
}

非侵入性集成

如果我们不想(或不能)修改我们想持久化的类,只要我们有恢复其状态所需的信息,我们仍然可以使用Pickles。以原始的Counter类为例;我们可以通过编写几个类外的方法来支持基本的“pickle”:

class Counter {
  int count;

  Counter([final int start = 0]) : count = start;

  int countUp() => count++;
}

// 在其他地方...

Pickle counterToPickle(final Counter counter) => PickleBuilder().withInt('start', counter.count).build();

Counter counterFromPickle(final Pickle pickle) => Counter(pickle.readInt('start'));

这样,我们将无法获得嵌套Pickles的良好集成支持,但我们仍然可以创建Pickles并恢复Counter到以前的状态:

void main() {
  var counter = Counter();
  print(counter.countUp()); // 输出: 0

  var pickle = counterToPickle(counter);
  print(counter.countUp()); // 输出: 1

  counter = counterFromPickle(pickle);
  print(counter.countUp()); // 输出: 0
}

当然,我们仍然可以实现一个使用这些方法进行嵌套Pickling的Version类:

class Version implements Pickleable {
  final Counter _counter;

  Version() : _counter = Counter();

  Version._(final this.counter);

  String nextVersion() => 'v${_counter.countUp()}';

  [@override](/user/override)
  Pickle toPickle() => PickleBuilder().withPickle('counter', counterToPickle(_counter)).build();

  static Version fromPickle(final Pickle pickle) => Version._(pickleToCounter(pickle.readPickle('counter')));
}

根据项目需求选择正确的集成级别,并享受乐趣吧!


示例代码

import 'dart:io';

import 'package:fling_pickle/fling_pickle.dart';

// 对于完全集成,实现Pickleable接口并支持静态方法从Pickles创建实例(构造函数可以工作,但由于Dart不支持函数指针到构造函数,你会错过一些语法糖功能)。
class Color implements Pickleable {
  final int r;
  final int g;
  final int b;

  const Color(this.r, this.g, this.b);

  [@override](/user/override)
  String toString() {
    return '($r, $g, $b)';
  }

  //----------start Added for SerDes----------//
  static Color fromPickle(final Pickle pickle) => Color(
        pickle.readInt('r'),
        pickle.readInt('g'),
        pickle.readInt('b'),
      );

  [@override](/user/override)
  Pickle asPickle() {
    return PickleBuilder()
        .withInt('r', r)
        .withInt('g', g)
        .withInt('b', b)
        .build();
  }
//----------end Added for SerDes----------//
}

// 如果你希望保持类干净无序列化代码,可以单独实现转换器(只要你有创建实例所需的信息)。你也可以递归转换。
class Banana {
  final String name;
  final double age;
  final Color favoriteColor;
  final List<Color> potentialColors;

  const Banana(
    this.name,
    this.age,
    this.favoriteColor,
    this.potentialColors,
  );

  [@override](/user/override)
  String toString() {
    return '$name is $age years old and likes the color $favoriteColor out of $potentialColors';
  }
}

//----------start Added for SerDes----------//
Pickle pickleBanana(final Banana banana) => PickleBuilder()
    .withString('name', banana.name)
    .withDouble('age', banana.age)
    .withPickleable('favoriteColor', banana.favoriteColor)
    .withPickleables('potentialColors', banana.potentialColors)
    .build();

Banana regurgitateBanana(final Pickle pickle) => Banana(
    pickle.readString('name'),
    pickle.readDouble('age'),
    pickle.readPickleable('favoriteColor', Color.fromPickle),
    pickle.readPickleables('potentialColors', Color.fromPickle));
//----------end Added for SerDes----------//

// 现在我们可以序列化和反序列化一个对象,即使它有嵌套的Pickleables。
void main() async {
  final fruit = Banana(
    'Yellow',
    1.5,
    Color(255, 255, 0),
    [Color(1, 2, 3), Color(4, 5, 6)],
  );
  print('I am a Banana: $fruit');

  // 要序列化对象,我们需要一个Pickler和对象的Pickles。
  final pickler = BinaryPickler();
  final pickle = pickleBanana(fruit);

  // 我们可以根据需要使用异步或同步序列化。
  final serializedPickle = pickler.writeSync(pickle);

  // 我们可以对结果的序列化pickle做任何我们喜欢的事情。
  final file = File('banana');
  file.writeAsBytesSync(serializedPickle);

  // 我们也可以异步或同步反序列化。
  final deserializedPickle = pickler.readSync(file.readAsBytesSync());

  // 反序列化后,我们回到了原来的对象。
  final regurgitatedFruit = regurgitateBanana(deserializedPickle);
  print('I am still a Banana: $regurgitatedFruit');

  file.deleteSync();
}

更多关于Flutter数据序列化与反序列化插件fling_pickle的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter数据序列化与反序列化插件fling_pickle的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用fling_pickle插件进行数据序列化与反序列化的示例代码。fling_pickle是一个用于Flutter的数据序列化与反序列化的插件,类似于Python中的pickle模块。

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

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

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

接下来,创建一个简单的Flutter应用来演示如何使用fling_pickle进行数据的序列化与反序列化。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Fling Pickle Demo'),
        ),
        body: Center(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: PickleDemo(),
          ),
        ),
      ),
    );
  }
}

class PickleDemo extends StatefulWidget {
  @override
  _PickleDemoState createState() => _PickleDemoState();
}

class _PickleDemoState extends State<PickleDemo> {
  String serializedData = '';
  Map<String, dynamic> originalData = {
    'name': 'Flutter',
    'version': '3.0.0',
    'isStable': true,
    'features': ['Null Safety', 'Sound Null Safety'],
  };

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('Original Data:'),
        Text('${originalData.toString()}'),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: () {
            // 序列化数据
            serializeData();
          },
          child: Text('Serialize Data'),
        ),
        SizedBox(height: 20),
        if (serializedData.isNotEmpty)
          Text('Serialized Data:'),
        if (serializedData.isNotEmpty)
          Text('${serializedData}'),
        SizedBox(height: 20),
        if (serializedData.isNotEmpty)
          ElevatedButton(
            onPressed: () {
              // 反序列化数据
              deserializeData();
            },
            child: Text('Deserialize Data'),
          ),
        SizedBox(height: 20),
        if (deserializedData != null)
          Text('Deserialized Data:'),
        if (deserializedData != null)
          Text('${deserializedData.toString()}'),
      ],
    );
  }

  void serializeData() {
    // 使用 FlingPickle 序列化数据
    final pickle = FlingPickle();
    pickle.encode(originalData).then((data) {
      setState(() {
        serializedData = String.fromCharCodes(data);
      });
    });
  }

  Map<String, dynamic> deserializedData;

  void deserializeData() {
    // 使用 FlingPickle 反序列化数据
    final pickle = FlingPickle();
    pickle.decode(Uint8List.fromList(serializedData.codeUnits)).then((result) {
      setState(() {
        deserializedData = result as Map<String, dynamic>;
      });
    });
  }
}

代码解释:

  1. 依赖添加:在pubspec.yaml文件中添加fling_pickle依赖。
  2. UI构建:创建了一个简单的Flutter应用,包含一个显示原始数据的Text控件和两个按钮,一个用于序列化数据,另一个用于反序列化数据。
  3. 序列化数据:在serializeData方法中,使用FlingPickleencode方法将原始数据序列化为字节数组,并将其转换为字符串显示。
  4. 反序列化数据:在deserializeData方法中,使用FlingPickledecode方法将之前序列化的数据反序列化为原始数据类型(这里是一个Map<String, dynamic>)。

注意:由于fling_pickle的具体API和实现可能会有所不同,因此请确保参考最新的官方文档和插件版本。如果插件的API有变化,请相应调整代码。

回到顶部