purebind 0.0.1
purebind: ^0.0.1 copied to clipboard
A next-generation, ultra-lightweight, fine-grained reactive state management framework for Flutter featuring type-safe signals, computed state, and undo/redo history.
PureBind โก #
A next-generation, ultra-lightweight, fine-grained reactive state management framework for Flutter built from first principles.
PureBind empowers Flutter developers with 100% type-safe signals, zero-boilerplate reactivity, automatic dependency tracking, native undo/redo history, and glitch-free atomic batching.
๐ฆ Installation #
Add purebind to your pubspec.yaml:
dependencies:
purebind: ^0.0.1
Or run:
flutter pub add purebind
๐ Core Highlights #
- ๐ Zero-Boilerplate Reactivity: Simple, intuitive
Pure(initialValue)signals with.valueand.update()access. - ๐ฏ Fine-Grained Sub-Tree Rebuilds: Target rebuilds exclusively to the smallest wrapped widget node using
PureBuilder. - ๐งฎ Computed Derived Signals: Create reactive values that automatically re-calculate whenever dependent signals mutate using
Pure<T>.computed. - โก Glitch-Free Atomic Batching: Group multiple signal updates into a single UI frame refresh via
Pure.batch(). - โช Built-In Time-Travel (Undo / Redo): Native state snapshot history with
.undo(),.redo(), and.canUndo. - ๐ Side-Effect Management: Handle dialogs, snackbars, and navigation smoothly with
PureConsumer. - ๐ฌ Selective Projections: Optimize performance by picking precise state projections with
PureSelect. - ๐ Context-Decoupled Architecture: Signals operate independently of
BuildContext, making them effortless to use in controllers, services, and unit tests. - ๐งน Automatic Lifecycle Management: Automatic subscriber detachment when widgets unmountโno manual disposal overhead.
- ๐ฑ 100% Multi-Platform: Works natively on iOS, Android, macOS, Windows, Linux, and Web (Wasm & JS).
๐ Supported Platforms #
Because PureBind is written in 100% Pure Dart & Flutter framework primitives with zero native dependencies or C/C++ channels, it is guaranteed to run everywhere Flutter runs:
| Platform | Support | Status |
|---|---|---|
| ๐ฑ iOS | Native | โ Supported |
| ๐ค Android | Native | โ Supported |
| ๐ป macOS | Native | โ Supported |
| ๐ช Windows | Native | โ Supported |
| ๐ง Linux | Native | โ Supported |
| ๐ Web (Wasm & JS) | Native | โ Supported |
- ๐ข Enterprise Ready: Full separation of UI and business logic, 100% testable controllers, and production-grade stability.
๐ข Enterprise Architecture & UI/Logic Separation #
PureBind enforces a strict Unidirectional Data Flow, separating UI rendering from background logic, API fetching, and domain services:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ UI LAYER (View) โ
โ StatelessWidget / PureBuilder / ListViews โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Dispatches User Action
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CONTROLLER / LOGIC LAYER โ
โ Pure Signals / Computed State / Async Methods โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. Calls API / Data Source
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DATA / BACKGROUND LAYER โ
โ Repositories / HTTP Clients / SQLite / Isolate Workers โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- UI Layer: Composed of pure
StatelessWidgets. The UI only reads state viaPureBuilderand dispatches user actions (e.g.controller.fetchUserData()). - Controller Layer: Handles business logic, input validation, and background processing. Updates reactive
Puresignals when tasks complete. - Data & Background Layer: Manages repositories, HTTP networking, background isolates, or local storage. Completely decoupled from Flutter UI.
๐ Quick Start Examples #
1. Basic Reactive Signal & Targeted Rebuild #
import 'package:flutter/material.dart';
import 'package:purebind/purebind.dart';
// Declare a reactive signal
final count = Pure<int>(0);
class CounterScreen extends StatelessWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: PureBuilder<int>(
pure: count,
builder: (context, value) => Text('Count: $value', style: const TextStyle(fontSize: 24)),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => count.value++,
child: const Icon(Icons.add),
),
);
}
}
2. Computed Derived Signals #
Computed signals automatically recalculate whenever their dependent signals change:
final itemPrice = Pure<double>(25.0);
final itemQuantity = Pure<int>(2);
// Automatically computes total whenever price or quantity mutates
final totalPrice = Pure<double>.computed(() => itemPrice.value * itemQuantity.value);
// Usage in UI
PureBuilder<double>(
pure: totalPrice,
builder: (context, total) => Text('Total: \$$total'),
);
3. Reactive List Collections #
Work directly with standard Dart collections:
final tasks = Pure<List<String>>(['Task 1', 'Task 2']);
// Add item
tasks.update((list) => list..add('Task 3'));
// Remove item
tasks.update((list) => list..removeAt(0));
// UI Binding
PureBuilder<List<String>>(
pure: tasks,
builder: (context, list) => ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) => ListTile(title: Text(list[index])),
),
);
4. Native Time-Travel (Undo / Redo) #
final textState = Pure<String>('Initial Text', enableHistory: true);
// Mutate state
textState.value = 'New Value';
// Time travel
if (textState.canUndo) textState.undo();
if (textState.canRedo) textState.redo();
5. Atomic Glitch-Free Batching #
Execute multiple signal updates simultaneously in a single UI frame update:
Pure.batch(() {
itemPrice.value = 50.0;
itemQuantity.value = 4;
});
6. Side-Effect Listening (PureConsumer) #
Use PureConsumer to execute side-effects like SnackBars, Dialogs, or Navigation without rebuilding widgets:
PureConsumer<String?>(
pure: errorMessageSignal,
listener: (context, message) {
if (message != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
},
child: const HomeScreen(),
);
7. Selective State Projection (PureSelect) #
Rebuild only when a specific property of a complex object changes:
class UserProfile {
final String name;
final int age;
UserProfile(this.name, this.age);
}
final userState = Pure<UserProfile>(UserProfile('Alice', 28));
// Rebuilds ONLY when 'name' changes, ignoring changes to 'age'
PureSelect<UserProfile, String>(
pure: userState,
selector: (user) => user.name,
builder: (context, name) => Text('Hello, $name!'),
);
8. FutureFirst / Async Signals (Pure.future) #
Reactive async signals automatically track connection states (waiting, done, error):
// Declare a Future-backed signal
final userProfile = Pure.future<String>(() async {
await Future.delayed(const Duration(seconds: 2));
return "User profile data loaded successfully!";
});
// UI Binding
PureBuilder<AsyncSnapshot<String>>(
pure: userProfile,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
return Text(snapshot.data ?? 'No data');
},
);
๐ Architectural Patterns #
Clean Architecture #
class UserController {
final UserRepository repository;
UserController(this.repository);
// Reactive State Signals
final users = Pure<List<User>>([]);
final isLoading = Pure<bool>(false);
final errorMessage = Pure<String?>(null);
Future<void> loadUsers() async {
isLoading.value = true;
errorMessage.value = null;
try {
users.value = await repository.fetchUsers();
} catch (e) {
errorMessage.value = e.toString();
} finally {
isLoading.value = false;
}
}
}
MVVM (Model-View-ViewModel) #
// ViewModel
class UserViewModel {
final UserRepository repository;
UserViewModel(this.repository);
final userList = Pure<List<User>>([]);
final isFetching = Pure<bool>(false);
// Computed state property exposed to View
late final totalUserCount = Pure<int>.computed(() => userList.value.length);
Future<void> fetchUsers() async {
isFetching.value = true;
userList.value = await repository.fetchUsers();
isFetching.value = false;
}
}
// View (StatelessWidget)
class UserView extends StatelessWidget {
final UserViewModel viewModel;
const UserView({super.key, required this.viewModel});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: PureBuilder<int>(
pure: viewModel.totalUserCount,
builder: (context, count) => Text('Users ($count)'),
),
),
body: PureBuilder<bool>(
pure: viewModel.isFetching,
builder: (context, fetching) {
if (fetching) return const CircularProgressIndicator();
return PureBuilder<List<User>>(
pure: viewModel.userList,
builder: (context, list) => ListView.builder(
itemCount: list.length,
itemBuilder: (context, i) => ListTile(title: Text(list[i].name)),
),
);
},
),
);
}
}
MVC (Model-View-Controller) #
// Controller
class ProductController {
final products = Pure<List<Product>>([]);
final cartCount = Pure<int>(0);
void addToCart(Product product) {
cartCount.value++;
}
}
// View
class ProductListView extends StatelessWidget {
final ProductController controller = ProductController();
ProductListView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
PureBuilder<int>(
pure: controller.cartCount,
builder: (context, count) => Chip(label: Text('$count items')),
),
],
),
body: PureBuilder<List<Product>>(
pure: controller.products,
builder: (context, list) => ListView.builder(
itemCount: list.length,
itemBuilder: (context, i) => ListTile(
title: Text(list[i].title),
trailing: IconButton(
icon: const Icon(Icons.add_shopping_cart),
onPressed: () => controller.addToCart(list[i]),
),
),
),
),
);
}
}
๐งช Unit Testing #
Testing Pure signals is completely decoupled from the Flutter widget tree. Write simple, ultra-fast Dart unit tests:
import 'package:flutter_test/flutter_test.dart';
import 'package:purebind/purebind.dart';
void main() {
test('Pure signal updates and triggers listeners', () {
final count = Pure<int>(0);
int callCount = 0;
count.update((val) => val + 1);
expect(count.value, equals(1));
});
test('Computed signal updates automatically', () {
final price = Pure<double>(10.0);
final qty = Pure<int>(2);
final total = Pure<double>.computed(() => price.value * qty.value);
expect(total.value, equals(20.0));
price.value = 15.0;
expect(total.value, equals(30.0));
});
}
๐ Credits & Acknowledgments #
PureBind is created and maintained with inspiration from modern reactive signal primitives and fine-grained dependency graph principles.
Special thanks to the Flutter and Dart open-source communities for continuous innovation in reactive UI architecture.
๐ License #
MIT License. Free for commercial and open-source projects.