crystalline 0.7.0
crystalline: ^0.7.0 copied to clipboard
Crystal clear state management
Crystalline #
Note: This library is currently in early development. Documentation is not yet ready, and the API is subject to changes as I continue to refine the core concepts.
The Core Idea #
Crystalline is a state management solution built on a simple premise: Everything that exists is data, and everything that is done is a manipulation of that data.
While many state management libraries focus on complex flows or boilerplate-heavy patterns, Crystalline treats state as a living piece of data that inherently reflects its own lifecycle. Whether data is being read, updated, created, or deleted, those transitions shouldn't just be "side effects"—they should be first-class properties of the state itself.
In Crystalline, state is not just a value; it's a Data object that knows its current operation, its failures, and its history. This makes the relationship between data manipulation and the UI observer transparent and effortless.
Quick Example #
Here is how you can manage an asynchronous operation like fetching a user profile.
1. Define and Manipulate Data #
Instead of manually managing loading booleans and error strings, the Data object tracks the state of the operation for you.
// Define a piece of state for a UserProfile
final userProfile = Data<UserProfile>();
// Perform an async operation
Future<void> fetchUserProfile() async {
// Set operation to 'read' to indicate loading
userProfile.operation = Operation.read;
try {
final profile = await api.getUserProfile();
// Setting the value automatically updates observers
userProfile.value = profile;
} catch (e) {
userProfile.failure = Failure(e.toString());
} finally {
// Reset operation to 'none' when finished
userProfile.operation = Operation.none;
}
}
2. Observe with DataBuilder #
DataBuilder gives you full control over how to render the state based on its current properties.
DataBuilder(
data: userProfile,
builder: (context, data) {
if (data.isReading) return CircularProgressIndicator();
if (data.hasFailure) return Text('Error: ${data.failure}');
if (data.hasValue) return Text('Welcome, ${data.value.name}');
return Text('No profile loaded');
},
)
3. Simplify with WhenDataBuilder #
For a more declarative approach, you can use WhenDataBuilder to handle different states of your data explicitly.
WhenDataBuilder(
data: userProfile,
onRead: (context, data) => CircularProgressIndicator(),
onFailure: (context, data) => Text('Error: ${data.failure}'),
onValue: (context, data) => Text('Welcome, ${data.value.name}'),
onNoValue: (context, data) => Text('No profile loaded'),
)
Features & Roadmap #
- Everything is Data: Crystalline provides specialized data classes like
Data,ListData,CollectionData, andOperationDatato handle different state shapes. - Built-in Builders: Reactive widgets like
DataBuilder,StoreBuilder, andWhenDataBuildermake it easy to consume state changes without manual listeners. - Store System: A structured
Storeclass to organize multiple states, with upcoming support for Code Generation to automatically produce custom data classes and builders. - Semantic Operations: State naturally tracks whether it is
isReading,isUpdating, or has afailure, allowing you to build robust UIs that respond to every stage of a data's lifecycle.
Crystalline exists to make state management feel like what it actually is: simple data manipulation.