kind 0.2.2 kind: ^0.2.2 copied to clipboard
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.
Overview #
This is Kind framework for serialization / persistence / reactive state management.
This is an initial experimental version. The APIs are not frozen.
Links #
What it gives you? #
- Convert graphs to/from JSON trees.
- Convert graphs to/from Protocol Buffers (and GRPC) trees.
- Use databases (upcoming).
- Our sibling package database will use this framework in future.
- Reactive programming
- The package has ReactiveSystem for observing views and mutations of reactive states in the isolate. JSON / Protobuf deserialization methods construct reactive objects by default.
- Various small helpers for data layer programming.
- Use kind.instanceValidate to
validate instances. For instance, if you have
StringKind(minLengthInRunes:3, maxLines:1)
, you get a debugging-friendly error message when you validate instance "a". - Use kind.randomExample to generate random instances of the kind.
- Frameworks may use kind.newList
to construct a memory-efficient lists without knowing the type. For instance,
Float32Kind, will
give you a dart:typed_data Float32List
rather than normal
List<double>
when you ask for a non-growable list. - Frameworks may use entityKind.meaning to understand kinds and properties in terms of other vocabularies (such as schema.org schemas).
- Use kind.instanceValidate to
validate instances. For instance, if you have
Overview of APIs #
Built-in kinds #
- Booleans
- Integers
- Floating-point numbers
- Date and time
- Strings and bytes
- Lists and sets
- Others
- Your custom models
- Recommended StringKind instances:
- stringKindForEmailAddress (email address)
- stringKindForMarkdown (Markdown formatted content)
- stringKindForPhoneNumber (phone number)
- stringKindForUrl (URL)
Data primitives #
- Date (like DateTime, but does not define time)
- DateTimeWithTimeZone (like DateTime, but allows any time zone)
- GeoPoint (geographical latitude/longitude coordinates)
- Uuid (128-bit object identifier)
Reactive collection classes #
Some alternatives #
For serialization #
- built_value
- Generates code for immutable values and JSON serialization.
- json_serializable
- Generates code for JSON serialization.
- protobuf
- Generates code for Protocol Buffers serialization.
For state management #
Getting started #
1.Adding dependency #
In pubspec.yaml, you should have something like:
environment:
sdk: '>=2.12.0-0 <3.0.0'
dependencies:
kind: ^0.2.2
2.Write data models #
In this example, we use Field helper to avoid writing reactive getters / setters:
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 #
// Encode
final json = person.getKind().jsonTreeEncode(person);
// Decode
final person = Person.kind.jsonTreeDecode(json);
Protocol Buffers #
// Encode
final generatedMessage = person.getKind().protobufTreeEncode(person);
// Decode
final person = Person.kind.protobufTreeDecode(generatedMessage);
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 class, without reactive state management #
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 class, with reactive state management #
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 class, without reactive state management #
// 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 class, with reactive state management #
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.
// ...