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

outdated

An unified data layer framework that enables serialization, persistence, and observability 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.2

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, and observability 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