kind 0.3.2 kind: ^0.3.2 copied to clipboard
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.
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.
Links #
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 #
- 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)
Other APIs #
- Date (unlike DateTime, has only date)
- DateTimeWithTimeZone (unlike DateTime, allows arbitrary time zone)
- GeoPoint (geographical latitude/longitude coordinates)
- ReactiveIterable
- ReactiveList
- ReactiveMap
- ReactiveSet
- Uuid (128-bit object identifier)
Some alternatives #
- For serialization
- For state management
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.
// ...