graft 1.0.0-alpha.3
graft: ^1.0.0-alpha.3 copied to clipboard
Self-optimizing state management engine for Flutter: BLoC-style domain models, Signals-level micro-rebuild precision, and register-level CPU bitmask diffing.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:graft/graft.dart';
// =============================================================================
// 1. STATE DEFINITION (Clean, zero-boilerplate domain model with GraftState)
// =============================================================================
class UserState extends GraftState {
String name;
String email;
bool isVerified;
int notificationCount;
UserState({
this.name = 'Alice Johnson',
this.email = 'alice@example.com',
this.isVerified = true,
this.notificationCount = 3,
});
@override
GraftProps get props => propsOf(name, email, isVerified, notificationCount);
}
// =============================================================================
// 2. GRAFT (Business Logic & State Transitions with direct cascade updates)
// =============================================================================
class UserGraft extends Graft<UserState> {
UserGraft() : super(UserState());
void updateName(String newName) {
state
..name = newName
..update();
}
void updateEmail(String newEmail) {
state
..email = newEmail
..update();
}
void toggleVerified() {
state
..isVerified = !state.isVerified
..update();
}
void incrementNotifications() {
state
..notificationCount += 1
..update();
}
/// Batched Multi-Property Update:
/// Updates name, email, verification, and notification count simultaneously
/// in a single statement. Triggers exactly ONE diff pass with zero intermediate rebuild glitches.
void batchUpdateProfile({
required String name,
required String email,
required bool isVerified,
required int notifications,
}) {
state
..name = name
..email = email
..isVerified = isVerified
..notificationCount = notifications
..update(); // ๐ฅ All 4 fields updated in 1 single atomic pass!
}
}
// -----------------------------------------------------------------------------
// Pattern A Models: Standard List (Single Graft State)
// -----------------------------------------------------------------------------
class TaskItem {
final String id;
final String title;
final bool isDone;
TaskItem({required this.id, required this.title, required this.isDone});
}
class TaskListState extends GraftState {
List<TaskItem> tasks;
TaskListState({required this.tasks});
@override
GraftProps get props => propsOf(tasks);
}
class TaskListGraft extends Graft<TaskListState> {
TaskListGraft()
: super(
TaskListState(
tasks: [
TaskItem(id: '1', title: 'Install Graft package', isDone: true),
TaskItem(
id: '2',
title: 'Learn graft.slot and graft.slots',
isDone: true,
),
TaskItem(
id: '3',
title: 'Explore ListView with and without ValueGraft',
isDone: false,
),
TaskItem(
id: '4',
title: 'Build high-performance Flutter app',
isDone: false,
),
],
),
);
void toggleTask(String id) {
state
..tasks = state.tasks
.map(
(t) => t.id == id
? TaskItem(id: t.id, title: t.title, isDone: !t.isDone)
: t,
)
.toList()
..update();
}
void addTask(String title) {
state
..tasks = [
...state.tasks,
TaskItem(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: title,
isDone: false,
),
]
..update();
}
}
// -----------------------------------------------------------------------------
// Pattern B Models: Micro-State (Per-Item ValueGraft)
// -----------------------------------------------------------------------------
class ProductItem {
final String title;
final ValueGraft<bool> isLiked;
final ValueGraft<int> quantity;
ProductItem({required this.title, bool liked = false, int count = 1})
: isLiked = ValueGraft<bool>(liked),
quantity = ValueGraft<int>(count);
}
class ProductCatalogState extends GraftState {
List<ProductItem> products;
bool isLoading;
ProductCatalogState({this.products = const [], this.isLoading = true});
@override
GraftProps get props => propsOf(products, isLoading);
}
class ProductCatalogGraft extends Graft<ProductCatalogState> {
ProductCatalogGraft() : super(ProductCatalogState());
/// Asynchronous, non-blocking initialization:
/// Simulates fetching items from an API or database in the background.
Future<void> init() async {
// Idempotent guard: do nothing if already loaded or in progress
if (state.products.isNotEmpty || !state.isLoading) return;
// Simulate non-blocking async network / I/O latency:
await Future<void>.delayed(const Duration(milliseconds: 600));
// Guard against disposal while async delay was pending:
if (isDisposed) return;
state
..products = List.generate(
25,
(i) => ProductItem(
title: 'Item #${i + 1} (Flutter Widget)',
liked: i % 2 == 0,
),
)
..isLoading = false
..update();
}
@override
void dispose() {
for (final p in state.products) {
p.isLiked.dispose();
p.quantity.dispose();
}
super.dispose();
}
}
// -----------------------------------------------------------------------------
// Pattern C Models: Independent Counter Graft for Multi-Graft Composition
// -----------------------------------------------------------------------------
class LiveCounterState extends GraftState {
int count;
LiveCounterState({this.count = 0});
@override
GraftProps get props => propsOf(count);
@override
void onReset() {
count = 0;
}
}
class LiveCounterGraft extends Graft<LiveCounterState> {
LiveCounterGraft() : super(LiveCounterState());
void increment() {
state
..count += 1
..update();
}
void decrement() {
state
..count -= 1
..update();
}
}
// -----------------------------------------------------------------------------
// Pattern C Models: Cascaded Domain Sub-Models (GraftModel) & Dry Persistence
// -----------------------------------------------------------------------------
class AddressModel extends GraftModel {
String street = '221B Baker Street';
String city = 'London';
int zipCode = 10001;
@override
GraftProps get props => propsOf(street, city, zipCode);
@override
void onDry() {
street = dry(street);
city = dry(city);
zipCode = dry(zipCode);
}
@override
void onReset() {
street = '221B Baker Street';
city = 'London';
zipCode = 10001;
}
}
class PersistentProfileState extends GraftState {
String fullName = 'Sherlock Holmes';
String notes = 'Consulting Detective';
bool isSyncing = false; // Transient UI state (starts fresh on app launch)
AddressModel address = AddressModel(); // Nested GraftModel created once!
@override
GraftProps get props => propsOf(fullName, notes, isSyncing, address);
@override
void onDry() {
fullName = dry(fullName);
notes = dry(notes);
address = dryModel(address); // Deep nested model persistence!
}
@override
void onReset() {
fullName = 'Sherlock Holmes';
notes = 'Consulting Detective';
isSyncing = false;
}
}
class PersistentProfileGraft extends Graft<PersistentProfileState> {
PersistentProfileGraft() : super(PersistentProfileState());
void updateName(String name) {
state
..fullName = name
..update();
}
void updateNotes(String notes) {
state
..notes = notes
..update();
}
void updateAddress({
required String street,
required String city,
required int zip,
}) {
state
..address.street = street
..address.city = city
..address.zipCode = zip
..update();
}
void toggleSyncing() {
state
..isSyncing = !state.isSyncing
..update();
}
void resetAll() {
state.reset();
}
}
// -----------------------------------------------------------------------------
// Pattern D Models: Enterprise Dependency Injection (DI) & Scoping
// -----------------------------------------------------------------------------
enum AppScope { checkout }
class DatabaseService implements Disposable {
bool isClosed = false;
DatabaseService._();
static Future<DatabaseService> open() async {
// Non-blocking asynchronous opening (never blocks Frame 0):
await Future.delayed(const Duration(milliseconds: 30));
return DatabaseService._();
}
void execute(String sql) {
if (isClosed) {
throw StateError('Cannot query: Database connection is closed!');
}
}
@override
void dispose() {
isClosed = true;
debugPrint('๐ฆ [DI Teardown 3] DatabaseService closed safely.');
}
}
class CartDao implements Disposable {
final DatabaseService db;
CartDao(this.db);
List<String> fetchItems() {
db.execute('SELECT * FROM cart');
return ['Single Origin Geisha', 'Pour-Over Kettle', 'Conical Burr Grinder'];
}
@override
void dispose() {
debugPrint('๐ [DI Teardown 2] CartDao disposed.');
}
}
class CheckoutCoordinator implements Disposable {
final CartDao cartDao;
CheckoutCoordinator(this.cartDao);
Future<void> submitOrder() async {
final items = cartDao.fetchItems();
debugPrint('๐ [Checkout] Order processed for: $items');
}
@override
void dispose() {
debugPrint('๐ณ [DI Teardown 1] CheckoutCoordinator disposed.');
}
}
class CheckoutState extends GraftState {
List<String> items = const [];
bool isSubmitting = false;
@override
GraftProps get props => propsOf(items, isSubmitting);
}
class CheckoutGraft extends Graft<CheckoutState> {
final CheckoutCoordinator _coordinator;
final CartDao _cartDao;
// ๐ Pure Constructor Injection:
CheckoutGraft({
required CheckoutCoordinator coordinator,
required CartDao cartDao,
}) : _coordinator = coordinator,
_cartDao = cartDao,
super(CheckoutState());
@override
void onInit() {
super.onInit();
state
..items = _cartDao.fetchItems()
..update();
}
Future<void> placeOrder(BuildContext context) async {
state
..isSubmitting = true
..update();
await _coordinator.submitOrder();
// โก Drop the entire checkout scope flow-wide:
AppScope.checkout.drop();
if (context.mounted) {
Navigator.of(context).pop();
}
}
}
// =============================================================================
// 3. MAIN ENTRY POINT & APP SETUP
// =============================================================================
void main() {
// 1. Enable colorized console dev logging:
Graft.observer = GraftDevObserver();
// 2. Configure enterprise DI root async database:
bindAsync<DatabaseService>(() => DatabaseService.open());
// 3. Configure scoped feature bindings:
scope(AppScope.checkout, () {
bind<CartDao>(() => CartDao(use<DatabaseService>()));
bind<CheckoutCoordinator>(() => CheckoutCoordinator(use<CartDao>()));
bind<CheckoutGraft>(() => CheckoutGraft(
coordinator: use<CheckoutCoordinator>(),
cartDao: use<CartDao>(),
));
});
// 4. Register Graft factories in DI:
GraftRegistry.register(UserGraft.new);
GraftRegistry.register(TaskListGraft.new);
GraftRegistry.register(ProductCatalogGraft.new);
GraftRegistry.register(LiveCounterGraft.new);
GraftRegistry.register(PersistentProfileGraft.new);
runApp(const GraftExampleApp());
}
class GraftExampleApp extends StatelessWidget {
const GraftExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Graft State Management Showcase',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
),
useMaterial3: true,
),
// 3. Attach navigator observer for route stack inheritance & auto-disposal:
navigatorObservers: [GraftRouteObserver()],
home: const HomeScreen(),
);
}
}
// =============================================================================
// 4. SCREEN 1: HOME SCREEN (First Initializer & Owner)
// =============================================================================
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
// Auto-instantiates and borrows across the navigation stack:
final graft = context.use(UserGraft.new);
final counter = context.use(LiveCounterGraft.new);
return Scaffold(
appBar: AppBar(
title: const Text('Graft Showcase ๐ฑ'),
actions: [
// Pre-flight derived computation: graft.compute
graft.compute(
compute: (s) => s.notificationCount,
builder: (count) => IconButton(
icon: Badge(
label: Text('$count'),
child: const Icon(Icons.notifications),
),
onPressed: () => graft.incrementNotifications(),
),
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// =================================================================
// Multi-Child Slot Diffing: graft.slots
// =================================================================
const _SectionHeader(
title: '1. Multi-Child Slot Diffing (graft.slots)',
subtitle:
'Only changed slots rebuild! Const widgets have 0 rebuilds.',
),
const SizedBox(height: 8),
graft.slots(
layout: const Column(children: slots),
slots: (s) => [
// Const widget directly without wrapping: 0 rebuilds, 0 closure evaluations
const Card(
color: Colors.deepPurple,
child: Padding(
padding: EdgeInsets.all(12),
child: Text(
'โก Const Header Banner (0 rebuilds)',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
Container(
padding: const EdgeInsets.all(12),
child: GraftBoundary(builder: (_) => Text(s.name)),
),
// Slot 1: Name
ListTile(
leading: const Icon(Icons.person, color: Colors.deepPurple),
title: Text('Name: ${s.name}'),
subtitle: const Text('Rebuilds ONLY when name changes'),
),
// Slot 2: Email
ListTile(
leading: const Icon(Icons.email, color: Colors.deepPurple),
title: Text('Email: ${s.email}'),
subtitle: const Text('0 rebuilds if email is unchanged'),
),
// Slot 3: Conditional Badge
if (s.isVerified)
const Chip(
avatar: Icon(Icons.verified, color: Colors.green),
label: Text('Verified User Account'),
)
else
const SizedBox.shrink(),
],
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton.icon(
icon: const Icon(Icons.edit),
label: const Text('Change Name'),
onPressed: () => graft.updateName(
graft.state.name == 'Alice Johnson'
? 'Bob Smith'
: 'Alice Johnson',
),
),
ElevatedButton.icon(
icon: const Icon(Icons.alternate_email),
label: const Text('Change Email'),
onPressed: () => graft.updateEmail(
graft.state.email == 'alice@example.com'
? 'bob@example.com'
: 'alice@example.com',
),
),
OutlinedButton.icon(
icon: const Icon(Icons.check_circle_outline),
label: const Text('Toggle Verified'),
onPressed: () => graft.toggleVerified(),
),
FilledButton.icon(
icon: const Icon(Icons.flash_on),
label: const Text('โก Batch Update All Fields'),
onPressed: () {
final isOriginal = graft.state.name == 'Alice Johnson';
graft.batchUpdateProfile(
name: isOriginal ? 'Dr. John Doe' : 'Alice Johnson',
email: isOriginal
? 'john.doe@company.org'
: 'alice@example.com',
isVerified: !graft.state.isVerified,
notifications: graft.state.notificationCount + 5,
);
},
),
],
),
const SizedBox(height: 24),
const Divider(),
// =================================================================
// Standard Flutter Widgets Composed with graft.slot
// =================================================================
const _SectionHeader(
title: '2. Standard Widgets Composed with graft.slot',
subtitle:
'Place graft.slot inside any Flutter widget (ListTile, Card, etc.).',
),
const SizedBox(height: 8),
ListTile(
leading: graft.slot(
builder: (s) => CircleAvatar(
backgroundColor: s.isVerified ? Colors.green : Colors.grey,
child: Text(s.name.isNotEmpty ? s.name[0] : '?'),
),
),
title: graft.slot(
builder: (s) => Text(
s.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
subtitle: graft.slot(builder: (s) => Text(s.email)),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
),
const SizedBox(height: 8),
Card(
color: Colors.deepPurple.shade50,
child: graft.slot(
builder: (s) => Padding(
padding: const EdgeInsets.all(12),
child: Text(
'Card slot diffed: ${s.name} (${s.isVerified ? "Verified" : "Unverified"})',
),
),
),
),
const SizedBox(height: 24),
const Divider(),
// =================================================================
// 3. GraftBoundary: Ambient Auto-Discovery & Insulation
// =================================================================
const _SectionHeader(
title:
'3. GraftBoundary: Multi-Graft Auto-Discovery & Insulation',
subtitle:
'Ambiently auto-discovers multiple Grafts with zero manual lists and full rebuild insulation: GraftBoundary(builder: (context) => ...)',
),
const SizedBox(height: 8),
_HeavyShellContainer(
child: GraftBoundary(
builder: (context) => Row(
children: [
const Icon(Icons.hub_outlined, color: Colors.deepPurple),
const SizedBox(width: 8),
Expanded(
child: Text(
'User: ${graft.state.name} โข Count: ${counter.state.count}',
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
IconButton(
icon: const Icon(Icons.add_circle,
color: Colors.deepPurple),
tooltip: 'Increment Counter from Multi-Graft Boundary',
onPressed: () => counter.increment(),
),
],
),
),
),
const SizedBox(height: 24),
const Divider(),
// =================================================================
// 4. Route-Stack Sharing & Lifecycle
// =================================================================
const _SectionHeader(
title: '4. Route-Stack Lifecycle & Inheritance',
subtitle:
'Pushed screens borrow the active Graft. Disposed when owner pops.',
),
const SizedBox(height: 12),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
),
icon: const Icon(Icons.open_in_new),
label: const Text('Push Edit Screen (Borrows Current Graft)'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const EditProfileScreen()),
);
},
),
const SizedBox(height: 8),
OutlinedButton.icon(
icon: const Icon(Icons.add_circle_outline),
label: const Text('Push Isolated Screen (context.create)'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const IsolatedProfileScreen(),
),
);
},
),
const SizedBox(height: 24),
const Divider(),
// =================================================================
// 4. ListView.builder Examples (With & Without ValueGraft)
// =================================================================
const _SectionHeader(
title: '4. ListView.builder Examples',
subtitle:
'Compare standard single-Graft lists vs. per-item ValueGraft.',
),
const SizedBox(height: 12),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal.shade700,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
),
icon: const Icon(Icons.format_list_bulleted),
label: const Text(
'Standard List (Single Graft / Without ValueGraft)',
),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const StandardListScreen()),
);
},
),
const SizedBox(height: 8),
OutlinedButton.icon(
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
),
icon: const Icon(Icons.bolt),
label: const Text('Micro-State List (Per-Item ValueGraft)'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const ValueGraftListScreen(),
),
);
},
),
const SizedBox(height: 24),
const Divider(),
// =================================================================
// 5. Multi-Graft Composition & Cross-Graft Nesting
// =================================================================
const _SectionHeader(
title: '5. Multi-Graft Composition (Cross-Graft Nesting)',
subtitle:
'Consume multiple Grafts in one screen with ZERO MultiBlocProvider pyramids.',
),
const SizedBox(height: 12),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.amber.shade800,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
),
icon: const Icon(Icons.hub),
label: const Text('Multi-Graft Showcase (Cross-Graft Nesting)'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const MultiGraftCompositionScreen(),
),
);
},
),
const SizedBox(height: 24),
const Divider(),
// =================================================================
// 6. Frame-0 Dry Persistence & Nested GraftModel
// =================================================================
const _SectionHeader(
title: '6. Frame-0 Persistence & Nested GraftModel',
subtitle:
'Cascade-mutable sub-models, selective persistence, zero-controller forms.',
),
const SizedBox(height: 12),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
),
icon: const Icon(Icons.save),
label: const Text('Frame-0 Dry Persistence & GraftModel Screen'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const PersistentModelScreen(),
),
);
},
),
const SizedBox(height: 24),
const Divider(),
// =================================================================
// 7. Enterprise Dependency Injection (DI) & Scoping
// =================================================================
const _SectionHeader(
title: '7. Enterprise Dependency Injection (DI)',
subtitle:
'Monotonic TypeId, reverse DAG teardown, and non-blocking async gates.',
),
const SizedBox(height: 12),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green.shade800,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
),
icon: const Icon(Icons.account_tree),
label: const Text('Enterprise DI & Scoped Checkout Screen'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const EnterpriseDIScreen(),
),
);
},
),
],
),
),
);
}
}
// =============================================================================
// 5. SCREEN 2: EDIT PROFILE SCREEN (Borrows Instance from Route Stack)
// =============================================================================
class EditProfileScreen extends StatelessWidget {
const EditProfileScreen({super.key});
@override
Widget build(BuildContext context) {
// Automatically reuses the exact same UserGraft created by HomeScreen!
final graft = context.use<UserGraft>();
return Scaffold(
appBar: AppBar(title: const Text('Borrower Screen ๐')),
body: Padding(
padding: const EdgeInsets.all(16),
child: graft.slots(
layout: const Column(children: slots),
slots: (s) => [
const Text(
'This screen called context.use<UserGraft>() and borrowed the existing instance from HomeScreen.',
style: TextStyle(fontSize: 15),
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Current Name: ${s.name}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text('Current Email: ${s.email}'),
const SizedBox(height: 4),
Text('Status: ${s.isVerified ? "Verified" : "Unverified"}'),
],
),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
graft.updateName('Edited by Second Screen!');
Navigator.of(context).pop();
},
child: const Text('Update Name & Pop Back to Home'),
),
],
),
),
);
}
}
// =============================================================================
// 6. SCREEN 3: ISOLATED SCREEN (Forces New Instance via context.create)
// =============================================================================
class IsolatedProfileScreen extends StatelessWidget {
const IsolatedProfileScreen({super.key});
@override
Widget build(BuildContext context) {
// Forces a brand new UserGraft owned specifically by this route:
final graft = context.create<UserGraft>();
return Scaffold(
appBar: AppBar(title: const Text('Isolated Screen ๐ก๏ธ')),
body: Padding(
padding: const EdgeInsets.all(16),
child: graft.slots(
layout: const Column(children: slots),
slots: (s) => [
const Text(
'This screen used context.create<UserGraft>() to create a completely independent instance.',
style: TextStyle(fontSize: 15),
),
const SizedBox(height: 16),
Text('Independent Name: ${s.name}'),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => graft.updateName('Isolated Change'),
child: const Text('Change (Does NOT Affect Home)'),
),
],
),
),
);
}
}
// =============================================================================
// 7. SCREEN 4: STANDARD LISTVIEW (Without ValueGraft โ Single Graft)
// =============================================================================
class StandardListScreen extends StatelessWidget {
const StandardListScreen({super.key});
@override
Widget build(BuildContext context) {
// Borrow or create route-scoped TaskListGraft:
final graft = context.use<TaskListGraft>();
return Scaffold(
appBar: AppBar(
title: const Text('Standard List (Single Graft)'),
actions: [
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Add Task',
onPressed: () {
graft.addTask('New Task #${graft.state.tasks.length + 1}');
},
),
],
),
body: Column(
children: [
// Pattern A Explanation Card
Card(
margin: const EdgeInsets.all(12),
color: Colors.teal.shade50,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: Colors.teal.shade200),
),
child: const Padding(
padding: EdgeInsets.all(12),
child: Text(
'๐ก Pattern A: 100% Lazy Virtualization with graft.builder & graft.item\n'
'โข 100% Lazy & Virtualized: Only visible items are in RAM (scales to 10,000+ items)!\n'
'โข Per-Item Diffing: Toggling a task rebuilds ONLY that 1 item โ 0 rebuilds for other items!\n'
'โข Universal: Use graft.builder for lists, or plug graft.item into GridView.builder / PageView.builder!',
style: TextStyle(fontSize: 13, height: 1.4),
),
),
),
Expanded(
child: graft.builder<TaskItem>(
items: (s) => s.tasks,
itemBuilder: (task, index) {
return Card(
child: ListTile(
leading: Checkbox(
value: task.isDone,
onChanged: (_) => graft.toggleTask(task.id),
),
title: Text(
task.title,
style: TextStyle(
decoration:
task.isDone ? TextDecoration.lineThrough : null,
color: task.isDone ? Colors.grey : null,
),
),
trailing: Chip(
label: Text(task.isDone ? 'Done' : 'Pending'),
backgroundColor: task.isDone
? Colors.green.shade100
: Colors.amber.shade100,
),
),
);
},
),
),
],
),
);
}
}
// =============================================================================
// 8. SCREEN 5: MICRO-STATE LISTVIEW (With ValueGraft per item)
// =============================================================================
class ValueGraftListScreen extends StatelessWidget {
const ValueGraftListScreen({super.key});
@override
Widget build(BuildContext context) {
// ๐ฅ 100% Stateless: Borrow or create route-scoped catalog Graft & trigger async init:
final catalog = context.use<ProductCatalogGraft>()..init();
return Scaffold(
appBar: AppBar(title: const Text('Micro-State List (ValueGraft)')),
body: Column(
children: [
// Pattern B Explanation Card
Card(
margin: const EdgeInsets.all(12),
color: Colors.deepPurple.shade50,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: Colors.deepPurple.shade200),
),
child: const Padding(
padding: EdgeInsets.all(12),
child: Text(
'๐ก Pattern B: Per-Item Micro-State with ValueGraft\n'
'โข 100% StatelessWidget! No StatefulWidget or setState required.\n'
'โข Non-blocking async init() simulates remote fetching (spinner -> list).\n'
'โข Once loaded, tapping "+" or "โฅ" rebuilds ONLY that specific cell slot โ 0 parent & sibling rebuilds!',
style: TextStyle(fontSize: 13, height: 1.4),
),
),
),
Expanded(
child: catalog.slot(builder: (s) {
if (s.isLoading) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 12),
Text('Loading products (non-blocking async)...'),
],
),
);
}
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8),
itemCount: s.products.length,
itemBuilder: (context, index) {
final product = s.products[index];
return Card(
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.deepPurple.shade100,
child: Text('${index + 1}'),
),
title: Text(product.title),
subtitle: product.quantity.slot(
// ๐ฅ Only this quantity label rebuilds when incremented/decremented!
builder: (qty) => Text('In Cart: $qty units'),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(
Icons.remove_circle_outline,
size: 20,
),
onPressed: () {
if (product.quantity.value > 1) {
product.quantity.value--;
}
},
),
IconButton(
icon: const Icon(
Icons.add_circle_outline,
size: 20,
),
onPressed: () => product.quantity.value++,
),
const SizedBox(width: 8),
product.isLiked.slot(
// ๐ฅ Only this heart icon rebuilds when toggled!
builder: (isLiked) => IconButton(
icon: Icon(
isLiked
? Icons.favorite
: Icons.favorite_border,
color: isLiked ? Colors.red : null,
),
onPressed: () => product.isLiked.value =
!product.isLiked.value,
),
),
],
),
),
);
},
);
}),
),
],
),
);
}
}
// =============================================================================
// 9. SCREEN 6: MULTI-GRAFT COMPOSITION & CROSS-GRAFT NESTING
// =============================================================================
class MultiGraftCompositionScreen extends StatelessWidget {
const MultiGraftCompositionScreen({super.key});
@override
Widget build(BuildContext context) {
// ๐ฅ ZERO TREE NESTING:
// Consume multiple independent Grafts directly at the top of any StatelessWidget!
// No MultiBlocProvider, no MultiProvider, no ConsumerWidget!
final userGraft = context.use<UserGraft>();
final counterGraft = context.use<LiveCounterGraft>();
return Scaffold(
appBar: AppBar(title: const Text('Multi-Graft Showcase ๐งฌ')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const _SectionHeader(
title: 'Multi-Graft Composition & Nested Isolation',
subtitle:
'Consume multiple Grafts with zero pyramids and complete rebuild isolation.',
),
const SizedBox(height: 16),
// Card explaining the behavior:
Card(
color: Colors.blue.shade50,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: Colors.blue.shade200),
),
child: const Padding(
padding: EdgeInsets.all(12),
child: Text(
'๐ก How it works:\n'
'โข Tapping "Change Name" updates ONLY the parent User Card slot. The amber Counter container below stays at 0 rebuilds!\n'
'โข Tapping "+1" updates ONLY the counter text slot. The parent User Card stays at 0 rebuilds!\n'
'โข Both Grafts are independent controllers with 100% mutual rebuild isolation.',
style: TextStyle(fontSize: 13, height: 1.4),
),
),
),
const SizedBox(height: 16),
userGraft.slots(
layout: const Column(children: slots),
slots: (userState) => [
// Slot 0: User Profile Card (Parent Graft)
Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(
Icons.account_circle,
color: Colors.deepPurple,
size: 28,
),
SizedBox(width: 8),
Text(
'Parent Graft: UserGraft',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
const Divider(height: 20),
Text(
'Name: ${userState.name}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'Email: ${userState.email}',
style: TextStyle(color: Colors.grey.shade700),
),
const SizedBox(height: 12),
ElevatedButton.icon(
icon: const Icon(Icons.edit),
label: const Text('Change Name'),
onPressed: () => userGraft.updateName(
userGraft.state.name == 'Alice Johnson'
? 'Dr. John Doe'
: 'Alice Johnson',
),
),
],
),
),
),
const SizedBox(height: 16),
// Slot 1: NESTED INDEPENDENT GRAFT (counterGraft.slots)
// Embedded inside a styled Container within UserGraft's slots!
// Thanks to GraftEquivalent, UserGraft updates do NOT rebuild this Container!
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.amber.shade100,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.amber.shade700, width: 2),
),
child: counterGraft.slots(
layout: const Column(children: slots),
slots: (counterState) => [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.bolt, color: Colors.amber.shade900),
const SizedBox(width: 6),
Text(
'Nested Child Graft: LiveCounterGraft',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.brown.shade900,
),
),
],
),
const SizedBox(height: 8),
Text(
'Count: ${counterState.count}',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.brown.shade900,
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton.filled(
style: IconButton.styleFrom(
backgroundColor: Colors.amber.shade800,
),
icon: const Icon(Icons.remove),
tooltip: 'Decrement',
onPressed: counterGraft.decrement,
),
const SizedBox(width: 16),
IconButton.filled(
style: IconButton.styleFrom(
backgroundColor: Colors.amber.shade800,
),
icon: const Icon(Icons.refresh),
tooltip: 'Reset to 0',
onPressed: counterGraft.reset,
),
const SizedBox(width: 16),
IconButton.filled(
style: IconButton.styleFrom(
backgroundColor: Colors.amber.shade800,
),
icon: const Icon(Icons.add),
tooltip: 'Increment',
onPressed: counterGraft.increment,
),
],
),
],
),
),
],
),
],
),
),
);
}
}
// =============================================================================
// HELPER WIDGETS
// =============================================================================
class _SectionHeader extends StatelessWidget {
final String title;
final String subtitle;
const _SectionHeader({required this.title, required this.subtitle});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
),
const SizedBox(height: 2),
Text(
subtitle,
style: TextStyle(fontSize: 13, color: Colors.grey.shade700),
),
],
);
}
}
class _HeavyShellContainer extends StatefulWidget {
final Widget child;
const _HeavyShellContainer({required this.child});
@override
State<_HeavyShellContainer> createState() => _HeavyShellContainerState();
}
class _HeavyShellContainerState extends State<_HeavyShellContainer> {
int _buildCount = 0;
@override
Widget build(BuildContext context) {
_buildCount++;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.deepPurple.shade200, width: 2),
boxShadow: [
BoxShadow(
color: Colors.deepPurple.withValues(alpha: 0.08),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'๐ก๏ธ Outer Parent Container',
style: TextStyle(fontWeight: FontWeight.bold),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: _buildCount == 1
? Colors.green.shade100
: Colors.red.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'Parent Builds: $_buildCount (Locked!)',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: _buildCount == 1
? Colors.green.shade800
: Colors.red.shade800,
),
),
),
],
),
const SizedBox(height: 8),
const Text(
'This parent container NEVER rebuilds when child state updates:',
style: TextStyle(fontSize: 13, color: Colors.black54),
),
const Divider(height: 20),
widget.child,
],
),
);
}
}
// =============================================================================
// 8. SCREEN 7: PERSISTENT MODEL SCREEN (GraftModel & Frame-0 Dry Persistence)
// =============================================================================
class PersistentModelScreen extends StatelessWidget {
const PersistentModelScreen({super.key});
@override
Widget build(BuildContext context) {
final profile = context.use(PersistentProfileGraft.new);
return Scaffold(
appBar: AppBar(
title: const Text('Frame-0 Dry Persistence ๐พ'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
tooltip: 'Reset to Defaults',
onPressed: profile.resetAll,
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Card(
color: Colors.blueGrey,
child: Padding(
padding: EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'โก Frame-0 L1 RAM Hydration',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
SizedBox(height: 4),
Text(
'Values below are hydrated in < 1.5 ยตs before Frame 0 renders. '
'Edit any field, press "R" for hot reload or restart the app โ '
'persistent fields restore instantly while transient UI state starts fresh!',
style: TextStyle(color: Colors.white70, fontSize: 13),
),
],
),
),
),
const SizedBox(height: 16),
// Live State Summary Card
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Live Domain State (Surgical Leaf Slots)',
style: TextStyle(fontWeight: FontWeight.bold),
),
const Divider(),
// Slot 1: Name
profile((s) => ListTile(
leading: const Icon(Icons.badge, color: Colors.blue),
title: Text('Name: ${s.fullName}'),
subtitle: const Text('Persisted via dry(fullName)'),
)),
// Slot 2: Notes
profile((s) => ListTile(
leading: const Icon(Icons.notes, color: Colors.blue),
title: Text('Notes: ${s.notes}'),
subtitle: const Text('Persisted via dry(notes)'),
)),
// Slot 3: Nested AddressModel
profile((s) => ListTile(
leading: const Icon(Icons.home, color: Colors.blue),
title: Text(
'Address: ${s.address.street}, ${s.address.city} (${s.address.zipCode})',
),
subtitle:
const Text('Persisted via dryModel(address)'),
)),
// Slot 4: Transient Syncing Status
profile((s) => ListTile(
leading: Icon(
s.isSyncing ? Icons.sync : Icons.cloud_done,
color: s.isSyncing ? Colors.orange : Colors.green,
),
title: Text(
s.isSyncing ? 'Syncing...' : 'Idle / In Sync',
),
subtitle: const Text(
'Transient UI state (starts fresh on launch)',
),
trailing: ElevatedButton(
onPressed: profile.toggleSyncing,
child: Text(s.isSyncing ? 'Stop' : 'Toggle'),
),
)),
],
),
),
),
const SizedBox(height: 20),
// Zero-Controller Form Fields
const Text(
'Zero-Controller Form Inputs (Frame 0 Bound)',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
TextFormField(
initialValue: profile.state.fullName,
decoration: const InputDecoration(
labelText: 'Full Name',
border: OutlineInputBorder(),
),
onChanged: profile.updateName,
),
const SizedBox(height: 12),
TextFormField(
initialValue: profile.state.notes,
decoration: const InputDecoration(
labelText: 'Notes',
border: OutlineInputBorder(),
),
onChanged: profile.updateNotes,
),
const SizedBox(height: 12),
TextFormField(
initialValue: profile.state.address.street,
decoration: const InputDecoration(
labelText: 'Street (Nested AddressModel)',
border: OutlineInputBorder(),
),
onChanged: (val) => profile.updateAddress(
street: val,
city: profile.state.address.city,
zip: profile.state.address.zipCode,
),
),
const SizedBox(height: 12),
TextFormField(
initialValue: profile.state.address.city,
decoration: const InputDecoration(
labelText: 'City (Nested AddressModel)',
border: OutlineInputBorder(),
),
onChanged: (val) => profile.updateAddress(
street: profile.state.address.street,
city: val,
zip: profile.state.address.zipCode,
),
),
],
),
),
);
}
}
// =============================================================================
// 9. SCREEN 8: ENTERPRISE DI & SCOPED CHECKOUT (Reverse DAG Teardown)
// =============================================================================
class EnterpriseDIScreen extends StatelessWidget {
const EnterpriseDIScreen({super.key});
@override
Widget build(BuildContext context) {
// ๐ก๏ธ GraftScope anchors AppScope.checkout to this route:
return GraftScope(
child: Scaffold(
appBar: AppBar(title: const Text('Enterprise DI & Scopes ๐')),
body: Builder(
builder: (innerContext) {
// Automatically resolves bound CheckoutGraft with injected dependencies:
final checkout = innerContext.use<CheckoutGraft>();
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Card(
color: Colors.teal,
child: Padding(
padding: EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'๐ก๏ธ In-Degree Reverse DAG Teardown',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
SizedBox(height: 4),
Text(
'CheckoutGraft was registered via bind<CheckoutGraft>() '
'with injected CartDao and CheckoutCoordinator. '
'When the order completes or screen pops, Graft automatically '
'disposes instances in topological reverse order: consumers first, '
'database last!',
style:
TextStyle(color: Colors.white70, fontSize: 13),
),
],
),
),
),
const SizedBox(height: 16),
const Text(
'Cart Items (Injected via CartDao):',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Expanded(
child: checkout(
(s) => ListView.builder(
itemCount: s.items.length,
itemBuilder: (ctx, i) => Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: const Icon(
Icons.coffee,
color: Colors.brown,
),
title: Text(s.items[i]),
subtitle:
const Text('Injected from DatabaseService'),
),
),
),
),
),
checkout(
(s) => ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green.shade800,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
icon: s.isSubmitting
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Icon(Icons.check_circle),
label: Text(
s.isSubmitting
? 'Processing & Dropping Scope...'
: 'Place Order & Drop AppScope.checkout',
style: const TextStyle(fontSize: 16),
),
onPressed: s.isSubmitting
? null
: () => checkout.placeOrder(innerContext),
),
),
],
),
);
},
),
),
);
}
}