dsi 1.0.3
dsi: ^1.0.3 copied to clipboard
Data Sync Ineterface - a tiny and powerfull state manager.
Data Sync Interface (DSI) #
DSI (Data Sync Interface) is a powerful, lightweight, and professional State Manager library for Flutter. It allows you to synchronize and share observable models, global values, and event callbacks between widgets seamlessly.
With DSI, you can significantly reduce boilerplate code, avoid memory leaks natively, and minimize repetitive setState logic, all without strong coupling.
🌟 Key Features #
- Observable Models: Register and update globally shared models with
DsiChangeNotifieryielding surgical optimizations (O(1) memory caching). - Context Extension: Use
context.dsi<Model>()directly to access and listen to state changes effortlessly. - Global Value Tracking: Reactive global scopes using
Dsi.valuesand listening efficiently insideDsiBuilder. - Event Bus (Callbacks): Dispatch named events and callbacks anywhere in your app via
Dsi.callback. - Smart Local UI State: Stop writing tedious boilerplate for local logic by leveraging
DsiUiValueMixinanduiValue.
📖 Table of Contents #
- Data Sync Interface (DSI)
🚀 Getting Started (Initialization) #
To use DSI throughout your application, you should utilize the DsiTreeObserver widget at the root of your app. This widget acts as an entry point for initializing global models effectively.
import 'package:flutter/material.dart';
import 'package:dsi/dsi.dart';
void main() {
runApp(
// Optionally pre-register multiple models efficiently:
DsiTreeObserver(
models: [ ThemeController(), AuthModel() ], // Registration
child: const MyApp(),
),
);
}
🧰 1. Observable Models (Core State) #
Instead of using Flutter's traditional ChangeNotifier, DSI introduces a highly optimized DsiChangeNotifier. It automatically handles context subscriptions and garbage collection (ignoring unmounted widgets) internally native through a highly efficient Set<BuildContext>.
Defining a Model #
import 'package:dsi/dsi.dart';
// 1. Extend the professional DsiChangeNotifier
class CounterModel extends DsiChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
// 2. Call trigger to rebuild ONLY dependent widgets!
notifyListeners();
}
}
Registering & Accessing #
Models must be registered so they can be available from anywhere.
You can register it at the application root using DsiTreeObserver (as seen above) or manually via Dsi.register().
// Register manually inside an initialization phase
Dsi.register(CounterModel());
In your widgets, you have two ways to retrieve the data safely:
@override
Widget build(BuildContext context) {
// Option A (Cleanest): Use the DsiExtension directly on the context
final counter = context.dsi<CounterModel>();
// Option B: Query DSI natively
final sameCounter = Dsi.of<CounterModel>(context);
return Scaffold(
body: Center(
child: Text('Count: ${counter?.count ?? 0}'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => counter?.increment(),
child: const Icon(Icons.add),
),
);
}
Updating Models Remotely #
If you are inside a function where you don't have access to your UI context, you can still update your models globally using Dsi.update<T>(). This will automatically trigger rebuilds on contexts listening to it across the app.
void performBackgroundSync() {
// Fetch data natively and update UI listeners seamlessly
Dsi.update<CounterModel>((model) {
model.increment();
return model;
});
}
🌐 2. Global Scoped Values (Dsi.values) #
Sometimes you just want to track a simple variable (a string, a boolean, or an int) across the app without constructing an entire Model Class. Dsi.values behaves like a Key-Value pair store that offers robust streaming!
Register & Use with DsiBuilder:
// 1. Register a value globally via key
Dsi.values.register<bool>(data: false, key: 'isDarkMode');
// 2. Build reactive UIs using DsiBuilder corresponding to the target key
Widget build(BuildContext context) {
return DsiBuilder<bool>(
idKey: 'isDarkMode',
builder: (context, isDark) {
return MaterialApp(
theme: isDark == true ? ThemeData.dark() : ThemeData.light(),
home: const Home(),
);
},
);
}
Modifying the value from anywhere:
// Change the value - This will automatically trigger DsiBuilder to build again!
Dsi.values.notifyTo<bool>('isDarkMode', true);
⚡ 3. Unified Callbacks (Dsi.callback) #
DSI allows you to map named callback events globally. This prevents deep "prop-drilling" of callback functions traversing across numerous constructor arguments.
// Register an event callback in Widget A:
Dsi.callback.register('onUserLogout', (payload) {
print('User Logged Out with message: $payload');
});
// Trigger the event safely from Widget B:
Dsi.callback.call('onUserLogout', payload: 'Session Expired!');
🎯 4. Smart Local State (DsiUiValueMixin) #
Are you tired of maintaining a verbose amount of setState(() {}) calls for local states like isLoading or isExpanded? The implementation of DsiUiValueMixin drastically accelerates simple local UI reactivity.
Quick implementation rules:
- Extend
with DsiUiValueMixinin yourStateclass. - Override
dsiStateUpdaterproperty, hooking it natively tosetState. - Track properties cleanly with
uiValue<T>(initial).
class SettingsScreen extends StatefulWidget {
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> with DsiUiValueMixin {
// 1. Delegate DSI Updater to Flutter's native setState handler
@override
void Function(void Function()) get dsiStateUpdater => setState;
// 2. Define reactive values cleanly (Use late syntax)
late final DsiUiValue<bool> isLoading = uiValue(false);
late final DsiUiValue<String> userStatus = uiValue('Active');
@override
Widget build(BuildContext context) {
return Column(
children: [
if (isLoading.value) const CircularProgressIndicator(),
Text("Status: ${userStatus.value}"), // Reactive text
ElevatedButton(
onPressed: () async {
// 3. Mutating the .value directly invokes setState securely!
isLoading.value = true;
userStatus.value = "Processing...";
await Future.delayed(const Duration(seconds: 2));
isLoading.value = false;
userStatus.value = "Finished";
},
child: const Text('Simulate Load'),
),
],
);
}
}
💡 Best Practices #
- Clean up manual listeners:
DsiChangeNotifiernatively prevents memory leaks automatically. However, if you explicitly attach subscriptions using rawlisten()logic insideinitState, always drop them viafreeIt()in yourdispose(). - One Ideology: Ensure to use
DsiChangeNotifierexplicitly instead of Flutter's stockChangeNotifier. Internal implementations depend heavily on targeted Context-mapping. - Optimized Lookup: DSI performs state searches instantly natively mapped in
O(1)Hash Maps. Name yourKeysdistinctly.
(License references and package data belong to their respective proprietary authors).