For detailed documentation, please visit DataFlow Documentation.
DataFlow is a powerful and flexible state management library for Flutter applications. It provides a simple and intuitive way to manage the flow of data and handle asynchronous operations in your app. With DataFlow, you can easily define actions, track their status, and update your UI accordingly.
DataFlow is designed to be lightweight, efficient, and easy to use. It leverages the power of Dart streams and follows a reactive programming paradigm to ensure smooth data flow and seamless UI updates.
- DataStore: Centralized state management for your application.
- DataAction: Define asynchronous operations as actions with customizable execution logic.
- DataSync: A widget that rebuilds its descendants based on the state of a DataStore.
- DataSyncNotifier: A widget that notifies listeners when specific DataActions occur.
- Middleware: Intercept and modify actions before and after execution.
- DataChain: You execute one action and based on its result you execute something else.
Feature/Library | DataFlow | Flutter Bloc | Provider | Riverpod | Signal | GetX |
---|---|---|---|---|---|---|
Centralized State Management | Yes | Yes | No | Yes | No | Yes |
Asynchronous Operations | Yes (DataAction) | Yes (Bloc) | No (requires FutureProvider) | Yes (StateNotifierProvider) | No | Yes |
Reactive UI Updates | Yes (DataSync) | Yes (BlocBuilder) | Yes (Consumer) | Yes (ConsumerWidget) | Yes (Reactive Programming) | Yes (Obx) |
Middleware Support | Yes | Yes | No | No | No | No |
Ease of Use | High | Medium | High | Medium | Medium | High |
Learning Curve | Low | High | Low | Medium | Medium | Low |
Boilerplate Code | Low | High | Low | Medium | Low | Low |
Built for Flutter | Yes | Yes | Yes | Yes | Yes | Yes |
Community Support | Growing | High | High | Growing | Growing | High |
Performance | High | High | High | High | High | - |
To start using DataFlow in your Flutter project, follow these steps:
- Add the
dataflow
package to yourpubspec.yaml
file:
dependencies:
dataflow: ^1.4.0
- Import the package in your Dart code:
import 'package:dataflow/dataflow.dart';
- Initialize DataFlow with a DataStore:
void main() {
DataFlow.init(MyDataStore());
runApp(MyApp());
}
- Define your DataActions:
class FetchDataAction extends DataAction<MyDataStore> {
@override
execute() async {
// Your data fetching logic here
await Future.delayed(Duration(seconds: 2));
print('Fetched Data');
}
}
- Use DataSync in your widgets:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('DataFlow Example')),
body: DataSync<MyDataStore>(
actions: {FetchDataAction},
loadingBuilder: (context) {
return const Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error) {
return Center(child: Text('An error occurred: $error'));
},
builder: (context, store, hasData) {
return LoginScreen();
},
),
);
}
}
- Trigger actions from your UI:
ElevatedButton(
onPressed: () {
FetchDataAction();
},
child: Text('Fetch Data'),
),
A DataAction represents an asynchronous operation in your application. It encapsulates the execution logic and provides a way to track the status of the action.
To define a DataAction, create a class that extends the DataAction
class and implement the execute
method:
class FetchDataAction extends DataAction {
@override
dynamic execute() async {
// Your data fetching logic here
await Future.delayed(Duration(seconds: 2));
print('Fetched Data');
}
}
The execute
method contains the actual logic for the action. It can perform any asynchronous operation, such as making API calls, querying a database, or processing data.
You can trigger a DataAction by calling it directly like:
FetchDataAction();
DataActions can also be chained together using the next
method:
FetchDataAction().next(() => ProcessDataAction());
A DataStore is a centralized repository for managing the state of your application. It extends the DataStore
class and can hold any data relevant to your app.
To create a DataStore, define a class that extends DataStore
:
class MyDataStore extends DataStore {
// Your application state here
String data = '';
}
You can access the DataStore from anywhere in your app using the DataFlow.getStore
method:
final store = DataFlow.getStore<MyDataStore>();
or
final store = context.getStore<MyDataStore>();
DataFlow is the core class that manages the flow of actions and notifies listeners of state changes. It is initialized with a DataStore and optional middleware.
To initialize DataFlow, call the DataFlow.init
method with your DataStore:
void main() {
DataFlow.init(MyDataStore());
runApp(MyApp());
}
DataFlow provides a stream of actions through the DataFlow.events
property. You can listen to this stream to react to action events:
DataFlow.events.listen((action) {
// Handle action events here
});
DataSync is a widget that rebuilds its descendants based on the state of a DataStore. It listens to specific actions and updates the UI accordingly.
To use DataSync, wrap your widget tree with the DataSync
widget and provide a builder function:
DataSync<AppStore>(
useDefaultWidgets: true,
loadingBuilder: (context) {
return const Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error) {
return Center(child: Text('An error occurred: $error'));
},
builder: (context, store, hasData) {
return store.isLoggedIn ? TodoScreen() : LoginScreen();
},
actions: const {LoginAction},
);
The builder function receives the current context, the DataStore, and the status of the actions. You can use this information to build your UI based on the state of the actions or you can use ::useDefaultWidgets:: property.
DataSyncNotifier is a widget that notifies listeners when specific DataActions occur. It is useful for performing side effects or triggering additional actions based on the status of an action.
To use DataSyncNotifier, wrap your widget tree with the DataSyncNotifier
widget and provide a map of actions and their corresponding listeners:
DataSyncNotifier(
actions: {
FetchDataAction: (context, store, status) {
// Handle the action status here
if (status == DataActionStatus.success) {
// Perform additional actions or side effects
}
},
},
child: MyChildWidget(),
),
The listeners will be called whenever the specified actions occur, allowing you to react to action status changes.
Middleware allows you to intercept and modify actions before and after their execution. It provides a way to add custom logic, logging, or error handling to your actions.
To create a middleware, define a class that extends DataMiddleware
and implement the preDataAction
and postDataAction
methods:
class LoggingMiddleware extends DataMiddleware {
@override
bool preDataAction(DataAction dataAction) {
print('Starting action: ${dataAction.runtimeType}');
return true;
}
@override
void postDataAction(DataAction dataAction) {
print('Finished action: ${dataAction.runtimeType} with status ${dataAction.status}');
}
}
The preDataAction
method is called before the action is executed, and the postDataAction
method is called after the action is executed.
To add middleware to DataFlow, pass a list of middleware instances to the DataFlow.init
method:
void main() {
DataFlow.init(MyDataStore(), middlewares: [LoggingMiddleware()]);
runApp(MyApp());
}
DataFlow provides built-in error handling for actions. If an exception occurs during the execution of an action, the action's status will be set to DataActionStatus.error
, and the error will be available through the error
property.
You can handle errors in your UI by checking the action status and displaying appropriate error messages:
DataSync<MyDataStore>(
actions: {FetchDataAction},
builder: (context, store, hasData) {
if (context.dataSync().hasAnyActionError) {
return const Center(child: Text('An error occurred'));
}
// Rest of your UI
},
),
You can also handle errors in middleware by implementing custom error handling logic in the postDataAction
method:
class ErrorHandlingMiddleware extends DataMiddleware {
@override
void postDataAction(DataAction dataAction) {
if (dataAction.status == DataActionStatus.error) {
// Handle the error here
print('Error: ${dataAction.error}');
}
}
}
Here are some best practices to follow when using DataFlow:
- Keep your DataActions focused and single-purpose. Each action should represent a specific operation or task.
- Use meaningful names for your DataActions and DataStores to improve code readability.
- Leverage middleware for cross-cutting concerns like logging, error handling, or authentication.
- Use DataSync to rebuild your UI based on action status changes, and DataSyncNotifier for side effects and additional actions.
- Handle errors gracefully and provide meaningful error messages to the user.
- Avoid excessive nesting of actions using the
next
method. Keep the action flow simple and linear.
The above comparison table provides an overview of how DataFlow stacks up against other popular state management libraries for Flutter. For more detailed information and advanced usage, please refer to the official documentation.
DataFlow provides a powerful and flexible way to manage the state and data flow in your Flutter applications. By defining actions, using a centralized store, and leveraging widgets like DataSync and DataSyncNotifier, you can create reactive and responsive UIs with ease.
This documentation covers the core concepts and usage of DataFlow. For more advanced scenarios and detailed API reference, please refer to the official documentation.
Happy coding with DataFlow!