kind 0.3.1 copy "kind: ^0.3.1" to clipboard
kind: ^0.3.1 copied to clipboard

outdated

An unified data layer framework that enables serialization / persistence / state management for any Dart class (without code generation!). Contains common primitives such as GeoPoint and UUID.

Pub Package Github Actions CI

Overview #

An unified data layer framework that enables serialization, persistence, and state management for any Dart class. This an early version and the APIs are not frozen. The package requires a null-safe Flutter SDK 2.0.0 or later.

Features #

  • Encode/decode JSON.
    • The package can handle most JSON serialization requirements.
  • Encode/decode Protocol Buffers.
    • The package can handle most Protocol Buffers (and GRPC) serialization requirements.
  • Use databases (upcoming).
    • Our sibling package database will use this framework in future.
  • Observe views / mutations in graphs
    • The package has ReactiveSystem for observing views and mutations of reactive states in the isolate.
    • When you are deserializing JSON or Protocol Buffers, you get reactive objects by default.

Overview of APIs #

Built-in kinds #

Other APIs #

Some alternatives #

Getting started #

1.Adding dependency #

In pubspec.yaml, you should have something like:

environment:
  sdk: '>=2.12.0 <3.0.0'

dependencies:
  kind: ^0.3.0

2.Write data models #

In the following example, we use Field. Wrapping values inside Field simplifies state observation. If you want to use normal Dart getters / setters, see "Alternative approaches" section below.

class Person extends Entity {
  static final EntityKind<Person> kind = EntityKind<Person>(
    name: 'Person',
    build: (b) {
      b.optionalString(
        id: 1,
        name: 'fullName',
        minLength: 1,
        field: (e) => e.fullName,
      );
      b.requiredSet<Person>(
        id: 2,
        name: 'friends',
        itemsKind: Person.kind,
        field: (e) => e.friends,
      );
      b.constructor = () => Person();
    },
  );

  /// Full name.
  late final Field<String?> fullName = Field<String?>(this);

  /// Friends.
  late final SetField<Person> friends = SetField<Person>(this);

  @override
  EntityKind getKind() => kind;
}

Serialization #

JSON #

Use jsonTreeEncode(...) and jsonTreeDecode(...):

// Person --> JSON tree
final json = person.getKind().jsonTreeEncode(person);

// JSON tree --> Person
final person = Person.kind.jsonTreeDecode(json);

Protocol Buffers #

For encoding/decoding Protocol Buffers bytes, use protobufBytesEncode(...) and protobufBytesDecode(...):

// Person --> bytes
final generatedMessage = Person.kind.protobufBytesEncode(person);

// bytes --> Person
final person = Person.kind.protobufBytesDecode(bytes);

For encoding/decoding package:protobuf GeneratedMessage, use protobufTreeEncode(...) and protobufTreeDecode(...):

// Person --> GeneratedMessage
final generatedMessage = Person.kind.protobufTreeEncode(person);

// GeneratedMessage --> Person
final person = Person.kind.protobufTreeDecode(generatedMessage);

You can also generate GeneratedMessage classes with GRPC tooling and merge messages.

Alternative approaches to specifying data classes #

Why / why not? #

The alternative approaches:

  • Do not force you to deviate from the way you normally write classes.
  • Perform better when you have millions of objects.
  • Do not support reactive programming with ReactiveSystem unless you write a lot error-prone boilerplate code.
    • In future, we may release a code generator that generates boilerplate for you, but there will inevitably going to be some complexity unless Dart language designers decide to support something like decorator annotations.

Mutable and non-reactive #

You just define getter and setter in Prop for ordinary Dart fields:

class Person {
  /// Full name.
  String? fullName = '';

  /// Friends.
  final Set<Person> friends = {};
}

/// EntityKind for [Person].
final EntityKind<Person> personKind = EntityKind<Person>(
  name: 'Person',
  build: (builder) {
    builder.optionalString(
      id: 1,
      name: 'fullName',
      getter: (t) => t.fullName,
      setter: (t,v) => t.fullName = v,
    );
    builder.requiredSet<Person>(
      id: 2,
      name: 'friends',
      itemsKind: personKind,
      getter: (t) => t.friends,
    );
    builder.constructor = () => Person();
  },
);

Mutable and reactive #

You can use ReactiveMixin for implementing getters and setters that send notifications to ReactiveSystem:

class Person extends Entity with ReactiveMixin {
  String? _fullName;
  final Set<Person> _friends = ReactiveSet<Person>();

  /// Full name of the person.
  String? get fullName => beforeGet(_fullName);
  set fullName(String? value) => _fullName = beforeSet(_fullName, value);

  /// Friends of the person.
  Set<Person> get friends => beforeGet(_friends);

  @override
  EntityKind<Person> getKind() => personKind;
}

// The `personKind` is identical to the previous example.
// ...

Immutable and non-reactive #

// Extending Entity is optional, but recommended.
class Person {
  /// Full name of the person.
  final String? name;

  /// Friends of the person.
  final Set<Person> friends;

  Person({
    this.fullName,
    this.friends = const {},
  });
}

/// EntityKind for [Person].
final EntityKind<Person> personKind = EntityKind<Person>(
  name: 'Person',
  build: (builder) {
    final fullName = builder.optionalString(
      id: 1,
      name: 'fullName',
      getter: (t) => t.fullName,
    );
    final friends = builder.requiredSet<Person>(
      id: 2,
      name: 'friends',
      itemsKind: personKind,
      getter: (t) => t.friends,
    );
    builder.constructorFromData = (data) {
      return Person(
        name: data.get(fullName),
        friends: data.get(friends),
      );
    };
  },
);

Immutable and reactive #

You can use ReactiveMixin for implementing getters and setters that send notifications to ReactiveSystem:

// Extending Entity is optional, but recommended.
class Person extends Entity with ReactiveMixin {
  final String? _fullName;
  final Set<Person> _friends;

  /// Full name of the person.
  String? get fullName => beforeGet(_fullName);

  /// Friends of the person.
  Set<Person> get friends => beforeGet(_friends);

  Person({
    required String? name,
    Set<Person> friends = const {},
  }) :
    _fullName = name,
    _friends = ReactiveSet<Person>.wrap(friends);

  @override
  EntityKind<Person> getKind() => personKind;
}

// The `personKind` is identical to the previous example.
// ...
7
likes
0
pub points
18%
popularity

Publisher

verified publisherdint.dev

An unified data layer framework that enables serialization / persistence / state management for any Dart class (without code generation!). Contains common primitives such as GeoPoint and UUID.

Repository (GitHub)
View/report issues

License

unknown (LICENSE)

Dependencies

collection, fixnum, meta, protobuf

More

Packages that depend on kind