redus_flutter 0.7.0
redus_flutter: ^0.7.0 copied to clipboard
Vue-like Component system for Flutter with reactive state, lifecycle hooks, and dependency injection.
Redus Flutter #
Vue-like ReactiveWidget for Flutter with fine-grained reactivity, lifecycle hooks, and dependency injection.
Features #
- π― ReactiveWidget - Single-class component with state on Element and auto-reactivity
- π BindWidget - Lightweight widget with bind() and lifecycle (no auto-reactivity)
- ποΈ Observe - Widget that watches a source and rebuilds
- β‘ ObserveEffect - Widget that auto-tracks dependencies
- π Lifecycle Hooks - onMounted, onUpdated, onUnmounted, etc.
- π§© Composable Mixins - BindMixin, LifecycleHooks for custom widgets
- π Dependency Injection - Type + key-based lookup (from
redus) - π§Ή Auto Cleanup - Effect scopes tied to widget lifecycle
Installation #
dependencies:
redus_flutter: ^0.7.0
Quick Start #
ReactiveWidget with bind() #
Full reactivity with automatic dependency tracking in render():
import 'package:redus_flutter/redus_flutter.dart';
class CounterStore {
final count = ref(0);
void increment() => count.value++;
}
class Counter extends ReactiveWidget {
late final store = bind(() => CounterStore());
@override
void setup() {
onMounted((context) => print('Count: ${store.count.value}'));
}
@override
Widget render(BuildContext context) {
// Auto-tracks store.count.value - rebuilds automatically
return ElevatedButton(
onPressed: store.increment,
child: Text('Count: ${store.count.value}'),
);
}
}
BindWidget (Explicit Reactivity) #
Lightweight widget with bind() and lifecycle, but no auto-reactivity. Use Observe/ObserveEffect for reactive parts:
class Counter extends BindWidget {
late final count = bind(() => ref(0));
@override
void setup() {
onMounted((_) => print('Mounted!'));
}
@override
Widget build(BuildContext context) {
// Use Observe for reactive parts
return Observe<int>(
source: count.call,
builder: (_, value) => ElevatedButton(
onPressed: () => count.value++,
child: Text('Count: $value'),
),
);
}
}
Observe Widget #
Observe watches a reactive source (like watch()) and rebuilds when it changes:
final count = ref(0);
// Watch a Ref directly
Observe<int>(
source: count,
builder: (context, value) => Text('Count: $value'),
)
// Watch a derived value
Observe<int>(
source: () => count.value * 2,
builder: (context, doubled) => Text('Doubled: $doubled'),
)
// Watch multiple sources
ObserveMultiple<String>(
sources: [firstName, lastName],
builder: (context, values) => Text('${values[0]} ${values[1]}'),
)
ObserveEffect Widget #
ObserveEffect auto-tracks any reactive values (like watchEffect()):
final count = ref(0);
final name = ref('Alice');
// Auto-tracks all .value accesses
ObserveEffect(
builder: (context) => Column(
children: [
Text('Count: ${count.value}'),
Text('Name: ${name.value}'),
],
),
)
Fine-Grained .watch(context) #
Use .watch(context) in any widget for automatic rebuilds:
class MyStatelessWidget extends StatelessWidget {
final Ref<int> count;
@override
Widget build(BuildContext context) {
// Only THIS widget rebuilds when count changes
return Text('Count: ${count.watch(context)}');
}
}
Lifecycle Hooks #
| Hook | When |
|---|---|
onMounted |
After first build |
onUpdated |
After rebuild |
onUnmounted |
After dispose |
onBeforeMount |
Before first build |
onBeforeUpdate |
Before rebuild |
onBeforeUnmount |
Before dispose |
onDependenciesChanged |
When InheritedWidget deps change |
onAfterDependenciesChanged |
After processing dep changes |
onErrorCaptured |
Error boundary |
onActivated |
Widget activated |
onDeactivated |
Widget deactivated |
All lifecycle callbacks receive BuildContext as a parameter, allowing access to InheritedWidgets:
@override
void setup() {
onMounted((context) {
final theme = Theme.of(context);
final size = MediaQuery.of(context).size;
print('Mounted with screen width: ${size.width}');
});
onDependenciesChanged((context) {
// React to theme, locale, or media query changes
final brightness = Theme.of(context).brightness;
print('Theme changed to: $brightness');
});
// Use underscore if context not needed
onUpdated((_) => print('Widget updated'));
}
Composable Architecture #
The library uses a composable mixin architecture:
// ReactiveWidget = BindMixin + LifecycleHooks + auto-reactivity
abstract class ReactiveWidget extends Widget with BindMixin, LifecycleHooks { ... }
// BindWidget = BindMixin + LifecycleHooks (no auto-reactivity)
abstract class BindWidget extends Widget with BindMixin, LifecycleHooks { ... }
// Custom widget with just bind()
class MyWidget extends Widget with BindMixin {
late final state = bind(() => MyState());
// ...
}
Dependency Injection #
DI comes from redus package with key support:
// By type
register<ApiService>(ApiService());
final api = get<ApiService>();
// By key (multiple instances)
register<Logger>(ConsoleLogger(), key: #console);
register<Logger>(FileLogger(), key: #file);
final log = get<Logger>(key: #console);
When to Use What #
| Widget | Use When |
|---|---|
ReactiveWidget |
Full component with auto-reactivity, lifecycle, stores |
BindWidget |
State persistence + lifecycle, explicit reactivity control |
Observe<T> |
Watch specific source(s), explicit dependency |
ObserveEffect |
Auto-track multiple dependencies in builder |
.watch(context) |
Simple inline reactive values in any widget |
License #
MIT License