kinora_flow 1.0.3
kinora_flow: ^1.0.3 copied to clipboard
Kinora Flow Event Driven State Management
Kinora Flow - Event Driven State Management #
A powerful and flexible Event Driven State Management pattern implementation for Flutter applications. This package provides a reactive state management solution that promotes clean architecture, separation of concerns, and scalable application development, based on the work of Event-Component-System by Ehsan Rashidi.
π Features #
Core Architecture #
- ποΈ Event-Driven Architecture: Clean separation between data (
FlowState, where the app state is hold), behavior (FlowLogic, where business logic takes place), and events (FlowEvent, where communication occurs) - β‘ Reactive Programming: Automatic UI updates when
FlowStatechanges - π Event-Driven: Decoupled communication through events and reactive logic
- 𧬠Scoped Feature Management: Features are inherited through nested
FlowScopewidgets, with automatic disposal when a scope is removed, so features can be scoped - π― Type-Safe: Full type safety with Dart generics
- π§© Modular Design: Organize code into reusable features
Advanced Capabilities #
- π Built-in Inspector: Real-time debugging and visualization tools
- π Flow Analysis: Detect circular dependencies and cascade flows
- π Performance Monitoring: Track logic interactions and component changes
- π Comprehensive Logging: Detailed logic activity tracking
Developer Experience #
- π οΈ Widget Integration: Seamless Flutter widget integration
- π¨ Reactive Widgets: Automatic rebuilds on component changes
- π§ Debugging Tools: Visual inspector with filtering and search
- π Cascade Analysis: Understand data flow and dependencies
- βοΈ Hot Reload Support: Full development workflow integration
- π Component changes log:
π Quick Start #
Installation #
Add this package to your pubspec.yaml:
dependencies:
kinora_flow: ^1.0.0
Basic Usage #
1. Define FlowStates and Events
// FlowStates hold state data
class CounterState extends FlowState<int> {
CounterState([super.value = 0]);
}
// Events trigger actions
class IncrementEvent extends FlowEvent {
IncrementEvent();
}
2. Create Reactive Logic
// Logic define behavior and reactions
class IncrementCounterLogic extends FlowReactiveLogic {
IncrementCounterLogic();
// What triggers this logic?
@override
Set<Type> get reactsTo => {IncrementEvent};
// What is changed by this logic?
@override
Set<Type> get interactsWith => {CounterState};
// Business logic
@override
void react() {
final counter = manager.getComponent<CounterState>();
counter.update(counter.value + 1);
}
}
3. Organize into Features
class CounterFeature extends Feature {
CounterFeature() {
// Add components (States and Events)
addComponents({
CounterState(),
IncrementEvent(),
});
// Add logic
addLogic(IncrementCounterLogic());
}
}
4. Setup Flow Scope
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowScope(
features: {
CounterFeature(),
},
child: MaterialApp(
home: CounterPage(),
),
);
}
}
5. Create Reactive Widgets
class CounterPage extends FlowWidget {
@override
Widget build(BuildContext context, FlowContext flow) {
// Watch state changes
final counter = flow.watch<CounterState>();
// Get references to events
final incrementEvent = flow.get<IncrementEvent>();
return Scaffold(
appBar: AppBar(title: Text("Counter")),
body: Center(
child: Text("Count: ${counter.value}"),
),
floatingActionButton: FloatingActionButton(
// Trigger events (which will run all logic that
// `ractsTo` this event)
onPressed: incrementEvent.trigger,
child: Icon(Icons.add),
),
);
}
}
ποΈ Architecture Overview #
Core Concepts #
Components
Base building blocks that can be either FlowStates or FlowEvents:
- FlowState: Hold state data with automatic change notification
- FlowEvent: Trigger actions and logic reactions
Logic
Define behavior and business logic:
- FlowFeatureInitializationLogic: Setup tasks on feature initialization
- FlowFrameExecutionLogic: Frame-based continuous execution
- FlowReactiveLogic: React to component changes
- FlowCleanUpLogic: Cleanup tasks after each frame
- FlowFeatureDisposalLogic: Cleanup on feature disposal
Features
Organize related components and logic into cohesive modules:
class UserAuthFeature extends Feature {
UserAuthFeature() {
// States
addComponents({
AuthState(),
LoginCredentialsState()
});
// Events
addComponents({
LoginEvent(),
LogoutEvent(),
});
// Logic
addLogics({
LoginUserReactiveLogic(),
LogoutUserReactiveLogic(),
});
}
}
Manager
Central coordinator that:
- Manages all features and their lifecycles
- Coordinates logic execution
- Handles component change notifications
- Provides component lookup and access
Logic Lifecycle #
graph TD
A[Feature Added] --> B[Feature Initialization Logic]
B --> C[Execute Logic]
C --> D[Component Changes]
D --> E[Reactive Logic]
E --> F[Cleanup Logic]
F --> C
C --> G[Feature Disposal Logic]
G --> H[Feature Disposed]
π― Advanced Features #
Scoped Feature Management #
Kinora Flow introduces true support for scoping features. When you nest FlowScope widgets, child scopes automatically inherit all features from their parent scopes.
This allows for a powerful and organized way to manage feature visibility and lifecycle. Features added to a specific FlowScope are automatically disposed of when that scope is popped from the widget tree, preventing memory leaks and ensuring a clean state.
Example of Nested Scopes:
// Root scope with a core feature
FlowScope(
features: {CoreFeature()},
child: SomeWidget(
child: Builder(
builder: (context) {
// Nested scope for a specific part of the app
return FlowScope(
features: {DashboardFeature()},
child: DashboardPage(), // DashboardPage can access both CoreFeature and DashboardFeature
);
},
),
),
);
In this example, DashboardPage and its children can access components, events, and logic from both CoreFeature and DashboardFeature. When the nested FlowScope is removed from the tree, DashboardFeature and all its resources will be disposed of automatically.
Reactive Widget Integration #
FlowWidget
Automatically rebuilds when watched components change:
class ProfileWidget extends FlowWidget {
@override
Widget build(BuildContext context, FlowContext flow) {
final user = flow.watch<UserState>();
final auth = flow.watch<AuthState>();
return Column(
children: [
Text("Welcome ${user.value.name}"),
Text("Status: ${auth.value}"),
],
);
}
}
FlowBuilder
Functional approach for simple reactive widgets:
FlowBuilder<UserState>(
builder: (context, flow) {
final user = flow.watch<UserState>();
return Text("Hello ${user.value.name}");
},
)
FlowStatefulWidget
For complex widgets requiring local state (ex.: TextEditingController):
class ComplexWidget extends FlowStatefulWidget {
@override
State<ComplexWidget> createState() => _ComplexWidgetState();
}
class _ComplexWidgetState extends FlowStatefulWidgetState<ComplexWidget> {
@override
Widget build(BuildContext context) {
final data = flow.watch<DataState>();
return YourComplexWidget(data: data.value);
}
}
Event Handling and Listeners #
Direct Event Listening
class NotificationWidget extends FlowWidget {
@override
Widget build(BuildContext context, FlowContext flow) {
// Listen to specific events
flow.listen<ErrorEvent>((error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error.value.message)),
);
});
return YourWidget();
}
}
Lifecycle Callbacks
class LifecycleWidget extends FlowWidget {
@override
Widget build(BuildContext context, FlowContext flow) {
flow.onEnter(() {
print("Widget entered Flow context");
});
flow.onExit(() {
print("Widget exited Flow context");
});
return YourWidget();
}
}
Logic Types and Usage #
FlowReactiveLogic
Respond to component changes:
class ValidationLogic extends FlowReactiveLogic {
ValidationLogic();
@override
Set<Type> get reactsTo => {FormDataState};
@override
Set<Type> get interactsWith => {ValidationState};
@override
bool get reactsIf => true; // Conditional reactions
@override
void react() {
final formData = manager.getComponent<FormDataState>();
final validation = manager.getComponent<ValidationState>();
// Validate form data
final isValid = validateForm(formData.value);
validation.update(isValid);
}
}
ExecuteLogic
Continuous frame-based execution:
class TimerLogic extends FlowFrameExecutionLogic {
TimerLogic();
@override
Set<Type> get interactsWith => {TimerState};
@override
void execute(Duration elapsed) {
final timer = manager.getComponent<TimerState>();
timer.update(timer.value + elapsed.inMilliseconds);
}
}
Initialize/Dispose Logic
Setup and cleanup:
class DatabaseInitLogic extends FlowFeatureInitializationLogic {
@override
Set<Type> get interactsWith => {DatabaseState};
@override
void initialize() {
// Initialize database connection
print("Database initialized");
}
}
class DatabaseDisposalLogic extends FlowFeatureDisposalLogic {
@override
Set<Type> get interactsWith => {DatabaseState};
@override
void dispose() {
// Close database connection
print("Database closed");
}
}
π Debugging and Inspection #
Inspector #
Real-time debugging interface with four main views:
Summary View
- Cascade analysis and flow detection
- Circular dependency warnings
- Logic interaction overview
- Performance metrics
Components View
- Live component inspection
- FlowState value monitoring
- Event triggering interface
- Filtering by feature and type
Logs View
- Logic reaction tracking
- Component change history
- Filterable log levels
- Stack trace analysis
Inspector Integration #
MaterialApp(
home: YourHomePage(),
builder: (context, child) {
return Row(
children: [
Expanded(child: child ?? SizedBox()),
Expanded(child: Inspector()), // Add inspector
],
);
},
)
Custom Inspector Widgets #
Components can provide custom inspector representations:
class UserState extends FlowState<User> {
UserState(super.value);
@override
Widget buildInspector(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Name: ${value.name}"),
Text("Email: ${value.email}"),
Text("Role: ${value.role}"),
],
);
}
}
Logging and Analysis #
Custom Logging
// Log custom events
Logger.log(CustomLog(
time: .now(),
level: .info,
message: "Custom event occurred",
stack: .current,
));
Cascade Analysis
final manager = FlowScope.of(context);
final analysis = Analyzer.analyze(manager);
// Get cascade flows
final flows = analysis.getCascadeFlowsFrom(UserLoginEvent);
// Check for circular dependencies
final circular = analysis.getCircularDependencies();
// Validate logic health
final issues = analysis.validateCascadeLogic();
π Real-World Example #
User Authentication System #
// States
class AuthState extends FlowState<Auth> {
AuthState() : super(Auth.unauthenticated);
}
class LoginCredentialsState extends FlowState<LoginCredentials> {
LoginCredentialsState() : super(LoginCredentials.empty());
}
// Events
class LoginEvent extends FlowEvent {
LoginEvent();
}
class LogoutEvent extends FlowEvent {
LogoutEvent();
}
// Logic
class LoginUserReactiveLogic extends FlowReactiveLogic {
LoginUserReactiveLogic();
@override
Set<Type> get reactsTo => {LoginEvent};
@override
Set<Type> get interactsWith => {AuthState, LoginCredentialsState};
@override
void react() async {
final credentials = manager.getComponent<LoginCredentialsState>();
final authState = manager.getComponent<AuthState>();
try {
authState.update(Auth.loading);
final user = await authenticateUser(credentials.value);
authState.update(Auth.authenticated(user));
} catch (error) {
authState.update(Auth.error(error.toString()));
}
}
}
// Feature
class UserAuthFeature extends Feature {
UserAuthFeature() {
addComponents({
AuthState(),
LoginCredentialsState(),
LoginEvent(),
LogoutEvent(),
});
addLogics({
LoginUserReactiveLogic(),
LogoutUserReactiveLogic(),
});
}
}
// UI Integration
class LoginPage extends FlowWidget {
@override
Widget build(BuildContext context, FlowContext flow) {
final authState = flow.watch<AuthState>();
final credentials = flow.get<LoginCredentialsState>();
final loginEvent = flow.get<LoginEvent>();
return Scaffold(
appBar: AppBar(title: Text("Login")),
body: Column(
children: [
if (authState.value.isLoading)
CircularProgressIndicator(),
TextField(
onChanged: (value) {
credentials.update(
credentials.value.copyWith(email: value)
);
},
decoration: InputDecoration(labelText: "Email"),
),
ElevatedButton(
onPressed: authState.value.isLoading ? null : loginEvent.trigger,
child: Text("Login"),
),
if (authState.value.hasError)
Text(
authState.value.error,
style: TextStyle(color: Colors.red),
),
],
),
);
}
}
π§ͺ Testing #
Testing States #
test('state notifies listeners on change', () {
final state = TestState();
bool notified = false;
state.addListener(TestListener(() => notified = true));
state.update(42);
expect(notified, isTrue);
expect(state.value, equals(42));
});
Testing Logic #
test('reactive logic processes events', () {
final manager = Manager();
final feature = TestFeature();
final logic = TestReactiveLogic();
feature.addComponent(TestEvent());
feature.addLogic(logic);
manager.addFeature(feature);
manager.getComponent<TestEvent>().trigger();
expect(logic.reacted, isTrue);
});
Testing Features #
test('feature manages components and logic', () {
final feature = TestFeature();
final state = TestState();
final logic = TestLogic();
feature.addComponent(state);
feature.addLogic(logic);
expect(feature.components, contains(state));
expect(feature.reactiveLogics[TestEvent], contains(logic));
});
Widget Testing #
testWidgets('Flow widget rebuilds on state change', (tester) async {
await tester.pumpWidget(
FlowScope(
features: {TestFeature()},
child: TestFlowWidget(),
),
);
final state = FlowScope.of(tester.element(find.byType(TestFlowWidget)))
.getComponent<TestState>();
state.update(100);
await tester.pump();
expect(find.text('100'), findsOneWidget);
});
π API Reference #
Core Classes #
Component
Base class for all components in the system.
Key Methods:
addListener(ComponentListener listener)- Add change listenerremoveListener(ComponentListener listener)- Remove listenerbuildInspector(BuildContext context)- Custom inspector widget
FlowState<T>
Stateful component that holds data of type T.
Key Methods:
update(T value, {bool notify = true})- Update state valueget value- Current state valueget previous- Previous state value
FlowEvent
Stateless component for triggering actions.
Key Methods:
trigger()- Fire the event and notify listeners
FlowFeature
Container for organizing related components and logic.
Key Methods:
addComponent(Component component)- Add component to featureaddComponents(Set<Component> components)- Add multiple componentsaddLogic(Logic logic)- Add logic to featureaddLogics(Set<Logic> logics)- Add multiple logics to featuregetComponent<T extends Component>()- Retrieve component by type
FlowManager
Central coordinator for the entire system.
Key Methods:
addFeature(Feature feature)- Add feature to managergetComponent<T extends Component>()- Get component from any featureinitialize()- Initialize all featuresteardown()- Cleanup all features
FlowContext
Widget-level interface for system access.
Key Methods:
get<T extends Component>()- Get component without watchingwatch<T extends Component>()- Watch component for changeslisten<T extends Component>(Function(T) listener)- Listen to component changesonEnter(Function() callback)- Widget lifecycle callbackonExit(Function() callback)- Widget disposal callback
Logic Types #
FlowReactiveLogic
Responds to component changes.
Abstract Members:
Set<Type> get reactsTo- Component types that trigger this logicSet<Type> get interactsWith- Component types this logic modifiesbool get reactsIf- Conditional reaction logicvoid react()- Reaction implementation
FlowFrameExecutionLogic
Runs continuously every frame.
Abstract Members:
Set<Type> get interactsWith- Component types this logic usesvoid execute(Duration elapsed)- Execution implementation
FlowFeatureInitializationLogic
Runs once during feature initialization.
Abstract Members:
Set<Type> get interactsWith- Component types this logic sets upvoid initialize()- Initialization implementation
FlowFeatureDisposalLogic
Runs once during feature disposal.
Abstract Members:
Set<Type> get interactsWith- Component types this logic cleans upvoid dispose()- Disposal implementation
FlowCleanUpLogic
Runs after each frame execution.
Abstract Members:
Set<Type> get interactsWith- Component types this logic cleansvoid cleanup()- Cleanup implementation
Widget Classes #
FlowScope
Provides scoped Flow context to widget tree.
Constructor:
FlowScope({
required Set<Feature> features,
required Widget child,
})
FlowWidget
Base class for reactive widgets.
Abstract Members:
Widget build(BuildContext context, FlowContext flow);
FlowBuilder<T>
Functional reactive widget builder.
Constructor:
FlowBuilder({
required Widget Function(BuildContext, FlowContext) builder,
})
FlowStatefulWidget
Base class for complex reactive widgets with local state.
Usage:
class MyWidget extends FlowStatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends FlowStatefulWidgetState<MyWidget> {
@override
Widget build(BuildContext context) {
final data = flow.watch<DataState>();
return YourWidget(data: data.value);
}
}
Analysis and Debugging #
Analyzer
Static analysis of system structure.
Key Methods:
static Analysis analyze(Manager manager)- Analyze system- Analysis methods for cascade flows and circular dependencies
Inspector
Visual debugging interface.
Constructor:
Inspector({
Duration refreshDelay = const Duration(milliseconds: 100),
})
Logger
System activity logging.
Key Methods:
static void log(Log log)- Add custom log entrystatic void clear()- Clear all logsstatic List<Log> get entries- Access log history
π§ Configuration #
Logger Configuration #
// Set maximum log entries
Logger.maxEntries = 2000;
// Custom log levels
class CustomLog extends Log {
final String message;
const CustomLog({
required this.message,
required super.time,
required super.level,
required super.stack,
});
@override
String get description => message;
}
Inspector Customization #
// Custom refresh rate
Inspector(
refreshDelay: Duration(milliseconds: 50),
)
// Component-specific inspector widgets
class UserState extends FlowState<User> {
UserState(super.value);
@override
Widget buildInspector(BuildContext context) {
return Card(
child: ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(value.avatarUrl),
),
title: Text(value.name),
subtitle: Text(value.email),
trailing: Chip(label: Text(value.role)),
),
);
}
}
π― Best Practices #
1. Feature Organization #
// β
Good: Organized by domain
features/
user_auth_feature/
states/
events/
logic/
user_auth_feature.dart
// β Avoid: Mixing concerns
features/
all_states.dart
all_events.dart
all_logic.dart
2. FlowState Design #
// β
Good: Immutable data structures
class UserState extends FlowState<User> {
UserState(super.value);
void updateName(String name) {
update(value.copyWith(name: name));
}
}
// β Avoid: Mutable data
class UserState extends FlowState<User> {
UserState(super.value);
void updateName(String name) {
value.name = name; // Don't mutate directly
notifyListeners(); // Manual notification
}
}
3. Logic Granularity #
// β
Good: Single responsibility
class ValidateEmailLogic extends FlowReactiveLogic {
@override
Set<Type> get reactsTo => {EmailState};
@override
void react() {
// Only validate email
}
}
class ValidatePasswordLogic extends FlowReactiveLogic {
@override
Set<Type> get reactsTo => {PasswordState};
@override
void react() {
// Only validate password
}
}
// β Avoid: Multiple responsibilities
class ValidateEverythingLogic extends FlowReactiveLogic {
@override
void react() {
// Validate email, password, phone, etc.
}
}
4. Error Handling #
// β
Good: Proper error handling
class LoginLogic extends FlowReactiveLogic {
@override
void react() async {
try {
final result = await authService.login();
authState.update(AuthState.authenticated(result));
} catch (error) {
errorState.update(ErrorState.fromException(error));
}
}
}
5. Testing Strategy #
// β
Good: Test logic in isolation
test('login logic authenticates user', () async {
final manager = Manager();
final feature = TestAuthFeature();
manager.addFeature(feature);
final loginEvent = manager.getComponent<LoginEvent>();
final authState = manager.getComponent<AuthState>();
loginEvent.trigger();
expect(authState.value, equals(AuthState.authenticated));
});
π Performance Tips #
1. Minimize Reactive Logic Scope #
// β
Good: Specific reactsTo
class UserProfileLogic extends FlowReactiveLogic {
@override
Set<Type> get reactsTo => {UserProfileState};
}
// β Avoid: Broad reactsTo
class UserProfileLogic extends FlowReactiveLogic {
@override
Set<Type> get reactsTo => {UserState, ProfileState, SettingsState};
}
2. Use Conditional Reactions #
class ExpensiveLogic extends FlowReactiveLogic {
@override
bool get reactsIf => shouldProcess();
bool shouldProcess() {
// Only react under specific conditions
return manager.getComponent<ConfigState>().value.enableExpensiveProcessing;
}
}
3. Optimize Widget Watching #
// β
Good: Watch only what you need
class UserWidget extends FlowWidget {
@override
Widget build(BuildContext context, FlowContext flow) {
final userName = flow.watch<UserNameState>();
final userAvatar = flow.get<UserAvatarState>(); // No rebuild on avatar change
return ListTile(
title: Text(userName.value),
leading: CircleAvatar(backgroundImage: userAvatar.value),
);
}
}
4. Batch State Updates #
// β
Good: Batch updates
void updateUserProfile(UserProfile profile) {
userNameState.update(profile.name, notify: false);
userEmailState.update(profile.email, notify: false);
userAvatarState.update(profile.avatar); // Only this triggers rebuild
}
π€ Contributing #
We welcome contributions!
π License #
This project is licensed under the MIT License - see the LICENSE file for details.
π Acknowledgments #
- Inspired by the Entity-Component-System pattern from game development built by Ehsan Rashidi
- Built for the Flutter community with β€οΈ
- Special thanks to all contributors and users and for Ehsan Rashidi
π Support #
- Issues: GitHub Issues
- Discussions: GitHub Discussions