Synchronize and share data, callbacks and global values between widgets and models without strong coupling.
Data Sync Interface (DSI)
DSI is a small Flutter/Dart library to synchronize and share data, callbacks and global values between widgets and models without strong coupling.
Main concepts
- Share observable models via
Dsi.register,Dsi.of()andDsi.update(). - Global values with
Dsi.values. - Named callbacks via
Dsi.callback. - Lightweight UI helpers via
DsiUiValueandDsiUiValueMixin.
Features
- Registration and update of models (supports extended
ChangeNotifier). - Automatic notifications to linked widgets.
- Management of shared values and listeners by key.
- Named callbacks for one-time communication between screens.
- Utilities to reduce
setStatecalls withDsiUiValueandDsiUiValueMixin.
Usage in example
- From the
examplefolder:
See the example main file: example/lib/main.dart
cd example
flutter pub get
flutter run
Quick usage
- Observable models (with
DsiChangeNotifier)
Model example (lib/models/counter_model.dart):
import 'package:dsi/dsi.dart';
class CounterModel extends DsiChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}
Model registration (e.g. in main.dart):
void main() {
Dsi.register(CounterModel());
runApp(const MyApp());
}
Access and update:
// Connect the app to DSI observer (important)
DsiTreeObserver(child: MaterialApp(...));
// reading
final counter = Dsi.of<CounterModel>(context);
Text('Count: ${counter?.count ?? 0}');
Text('Count: ${Dsi.of<CounterModel>(context)?.count}');
Text('Count: ${Dsi.model<CounterModel>(context)?.count}');
Text('Count: ${context.dsi<CounterModel>()?.count} : From DSI Extension');
// via model method
counter?.increment();
// or via Dsi.update
Dsi.update<CounterModel>((m) { m.count += 1; return m; });
Global values (Dsi.values)
// register a shared value
DsiValueInstance instance = Dsi.values.register<int>(data: 0, key: 'globalCount');
// notify listeners
Dsi.values.notifyTo<int>('globalCount', 42);
// listen
var sub = Dsi.values.listenTo<int>('globalCount', (v) => print(v));
// update
instance.value = 42;
// Get value instance
DsiValueInstance? instance = Dsi.values.get<int>('globalCount');
Named callbacks
// register
Dsi.callback.register('my_key', (payload) => print(payload));
// call from another screen
Dsi.callback.call('my_key', payload: 'message');
DsiUiValue and DsiUiValueMixin (new section)
Quick details:
DsiUiValue<T>is a small wrapper for local UI state that reduces verbosity aroundsetState.DsiUiValueMixinis a mixin to use onStatefulWidget'sStateto easily createDsiUiValueusing adsiStatergetter that must returnsetState.
Essential API (implementation in lib/src):
-
DsiUiValue<T>: constructorDsiUiValue(void Function(void Function()) setState, T initialValue)..value: read/write (writing triggerssetState)..silent: write without triggeringsetState..update(): force update (callssetState(() {})).
-
DsiUiValueMixin:- Declares abstract getter
void Function(void Function()) get dsiStater;thatStatemust implement (=> setState). uiValue<T>(T initial)returns aDsiUiValue<T>already bound todsiStater.uiUpdate([fn])performssetStateviadsiStater.
- Declares abstract getter
Usage example (in a StatefulWidget)
class SettingsScreen extends StatefulWidget {
const SettingsScreen({Key? key}) : super(key: key);
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> with DsiUiValueMixin {
// Bind dsiStater to this State's setState
@override
void Function(void Function()) get dsiStater => setState;
// late to initialize before initState if needed
late final DsiUiValue<bool> isLoading = uiValue(false);
// late var isLoading = uiValue(false);
@override
void initState() {
super.initState();
// Example: register a DSI callback
Dsi.callback.register('onReset', (_) {
Dsi.update<CounterModel>((m) { m.reset(); return m; });
});
}
@override
void dispose() {
Dsi.callback.unregister('onReset');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (isLoading.value) const CircularProgressIndicator(),
ElevatedButton(
onPressed: () async {
// change value and trigger rebuild
isLoading.value = true;
await Future.delayed(const Duration(seconds: 1));
isLoading.value = false;
},
child: const Text('Do work'),
),
],
),
),
);
}
}
MaterialApp - complete example
// lib/app.dart
import 'package:flutter/material.dart';
import 'package:dsi/dsi.dart';
import 'screens/counter_screen.dart';
import 'screens/settings_screen.dart';
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'DSI Demo',
theme: ThemeData(primarySwatch: Colors.blue),
initialRoute: '/',
routes: {
'/': (context) => const CounterScreen(),
'/settings': (context) => const SettingsScreen(),
},
builder: (context, child) {
// Example of using Dsi.values to manage a simple theme
Dsi.values.register<bool>(data: false, key: 'isDarkMode');
return DsiBuilder<bool>(
idKey: 'isDarkMode',
builder: (context, isDark) {
return Theme(
data: isDark ? ThemeData.dark() : ThemeData.light(),
child: child ?? const SizedBox.shrink(),
);
},
);
},
);
}
}
// lib/main.dart
void main() {
Dsi.registerModel(CounterModel());
runApp(const MyApp());
}
Useful files
- Export point:
lib/dsi.dart - Implementations:
lib/src/*(e.g.dsi_base.dart,dsi_change_notifier.dart,dsi_ui_value.dart,dsi_ui_value_mixin.dart) - Examples:
example/lib/...(example pages and models)
Best practices
- Use
DsiChangeNotifierfor your models to benefit from automatic notifications. - Use
DsiUiValue/DsiUiValueMixinto reduce the surface usage ofsetStateon local UI values. - Release resources (listeners, callbacks) in
dispose()ofStatefulWidgetwhen needed.
Contributing
- Open an issue or PR.
- Respect the license (see
LICENSE) and changelog (CHANGELOG.md).
License
BSD-3-Clause — see project LICENSE file.
Changelog
- See
CHANGELOG.md
Try the example
- Open a terminal in
example/:
cd example
flutter pub get
flutter run
- If you added the logo, check
pubspec.yamlandassets/.
Summary of changes
- Added documentation and examples for
DsiUiValueandDsiUiValueMixin. - Complete
MaterialAppexample showing DSI integration (model, values, callbacks, UI mixin). - Indication about
dsi_logo.pngasset andpubspec.yamlconfiguration.
Libraries
- dsi
- Support for doing something awesome.