redus_flutter 0.12.0
redus_flutter: ^0.12.0 copied to clipboard
Vue-like reactivity for Flutter with ref(), computed(), watch(), lifecycle hooks, and web-style routing.
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.12.0 - 2025-12-26 #
Added #
-
Redus Router - Web-framework-style reactive routing for Flutter:
-
RedusRouterConfig- Drop-in forMaterialApp.router()with new options:MaterialApp.router( routerConfig: RedusRouterConfig( routes: routes, defaultTransition: RouteTransition.fade(), layoutBuilder: (ctx, child) => Shell(child: child), notFoundBuilder: (loc) => NotFound(path: loc.path), beforeEach: authGuard, afterEach: analytics, observers: [AnalyticsObserver()], onInitialRoute: (route) => isLoggedIn ? route : '/login', ), ) -
New config options:
defaultTransition- Global page transition for all routeslayoutBuilder- Wrap all routes in a common layoutnotFoundBuilder- Custom 404 page builderobservers- Navigator observers for analytics/loggingonInitialRoute- Redirect initial route based on state
-
useRoute()/useRouter()- Context-free reactive hooks:final route = useRoute(); print(route.params['id']); useRouter().push('/users/123'); -
RouteConfig- Route definition with transitions and guards:RouteConfig( path: '/users/:id', builder: (_) => UserPage(), transition: RouteTransition.fade(), guards: [AuthGuard()], meta: {'requiresAuth': true}, ) -
RouteTransition- Page transition presets:RouteTransition.platform- Default Material/CupertinoRouteTransition.none- No animationRouteTransition.fade()- Fade in/outRouteTransition.slide()- Slide from directionRouteTransition.scale()- Scale from alignmentRouteTransition.custom()- Fully custom
-
RouteGuard- Navigation guards with sealed result type:class AuthGuard extends RouteGuard { Future<GuardResult> canActivate(to, from) async { if (!isLoggedIn) return GuardRedirect('/login'); return const GuardAllow(); } } -
RouterOutlet- Nested routing widget -
DeepLinkHandler- Deep linking with app_links:final initialLink = await DeepLinkHandler.getInitialLink(); DeepLinkHandler.listenToIncomingLinks();
-
Changed #
-
Folder reorganization - Library now organized into:
src/reactivity/- Widgets, mixins, extensionssrc/router/- Router components
-
New import options:
import 'package:redus_flutter/redus_flutter.dart'; // Everything import 'package:redus_flutter/reactivity.dart'; // Just reactivity import 'package:redus_flutter/router.dart'; // Just router
0.11.0 - 2025-12-25 #
Breaking Changes #
-
Renamed
ReactiveStatefulWidgettoReactiveWidget- The widget that supports Flutter mixins is now the primary reactive widget. -
Renamed
ReactiveWidgetStatetoReactiveState- The State class is now namedReactiveState. -
Removed old
ReactiveWidget- The old single-classReactiveWidgethas been removed. Use the newReactiveWidgetwithReactiveState. -
Removed
bind()functionality - Thebind()method andBindStateMixinhave been removed. Initialize your stores and refs insetup()instead.Migration:
// Before (old ReactiveWidget with bind) class Counter extends OldReactiveWidget { late final count = bind(() => ref(0)); late final store = bind(() => MyStore()); @override Widget render(BuildContext context) => Text('${count.value}'); } // After (new ReactiveWidget without bind) class Counter extends ReactiveWidget { const Counter({super.key}); @override ReactiveState<Counter> createState() => _CounterState(); } class _CounterState extends ReactiveState<Counter> { late final Ref<int> count; late final MyStore store; @override void setup() { count = ref(0); store = MyStore(); } @override Widget render(BuildContext context) => Text('${count.value}'); }
0.10.2 - 2025-12-25 #
Added #
-
@mustCallSuperannotations - Properly annotated on all overridable lifecycle methods inReactiveStateandReactiveWidgetState:initState()- Ensures setup and scope initializationdidUpdateWidget()- Ensures bind index reset on widget recreationactivate()- Ensures state expando is updated (ReactiveState only)dispose()- Ensures reactivity cleanup and scope disposalbuild()/reactiveBuild()- Ensures reactive tracking and mounted callbacks
This ensures subclasses correctly call
superto maintain proper lifecycle behavior.
0.10.1 - 2025-12-24 #
Added #
-
reactiveBuild(BuildContext context)- New helper method inReactiveWidgetStatethat encapsulates all reactive build logic. Makes it easier to overridebuild()for compatibility with Flutter mixins:class _KeepAliveState extends ReactiveWidgetState<KeepAliveWidget> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; @override Widget build(BuildContext context) { super.build(context); // Required for the mixin return reactiveBuild(context); // Full reactive functionality! } @override Widget render(BuildContext context) { return Text('Count: ${count.value}'); } }The method handles:
- Error state checking
- Reactive dependency tracking via
buildReactive - Error catching and
onErrorCapturedcallback invocation - Scheduling
onMountedcallbacks
0.10.0 - 2025-12-24 #
Added #
-
ReactiveStatefulWidgetandReactiveWidgetState<T>- New widget and state base class that supports Flutter's built-in State mixins:class AnimatedCounter extends ReactiveStatefulWidget { @override ReactiveWidgetState<AnimatedCounter> createState() => _AnimatedCounterState(); } class _AnimatedCounterState extends ReactiveWidgetState<AnimatedCounter> with SingleTickerProviderStateMixin { // ✅ Flutter mixin works! late final controller = AnimationController(vsync: this); late final count = bind(() => ref(0)); @override void setup() { onMounted(() => controller.forward()); onDispose(() => controller.dispose()); } @override Widget render(BuildContext context) { return Text('Count: ${count.value}'); } }Supported Flutter mixins include:
SingleTickerProviderStateMixin- For single animationsTickerProviderStateMixin- For multiple animationsAutomaticKeepAliveClientMixin- For keeping widget alive in listsRestorationMixin- For state restorationWidgetsBindingObserver- For app lifecycle events
0.9.0 - 2025-12-24 #
Changed (BREAKING) #
-
Lifecycle hooks simplified - Removed
(context)parameter from lifecycle callbacks for cleaner API:// Before onMounted((context) => print('Mounted!')); // After onMounted(() => print('Mounted!')); -
Lifecycle hook names changed to match Flutter semantics:
Old Name New Name onBeforeMountonInitStatewithtiming: beforeonBeforeUpdateonDidUpdateWidgetwithtiming: beforeonUpdatedonDidUpdateWidgetwithtiming: afteronBeforeUnmountonDisposewithtiming: beforeonUnmountedonDisposewithtiming: afteronActivatedonActivateonDeactivatedonDeactivateonDependenciesChangedonDidChangeDependencies -
ReactiveWidgetnow usesStatefulWidgetinternally:- State and lifecycle managed by
Stateclass internally - Developer API unchanged:
setup(),render(BuildContext),bind(), lifecycle hooks - More robust lifecycle handling, especially across parent rebuilds
- State and lifecycle managed by
Removed #
BindWidgetremoved - UseReactiveWidgetwithObserve/ObserveEffectfor explicit reactivity control
Added #
-
Timing control for lifecycle hooks - All hooks support
timingparameter:onInitState(() => print('before'), timing: LifecycleTiming.before); onInitState(() => print('after')); // default: after -
onDidUpdateWidgetreceives old and new widget:onDidUpdateWidget<MyWidget>((oldWidget, newWidget) { if (oldWidget.value != newWidget.value) { // Handle prop change } }); -
Consolidated mixins architecture:
LifecycleCallbacks- Callback storage and registrationLifecycleHooksStateMixin- Flutter lifecycle method overridesBindStateMixin- State persistence viabind()ReactiveStateMixin- Reactivity and effect scope
0.8.0 - 2025-12-24 #
Added #
-
LifecycleHooksStateMixin- Vue-like lifecycle hooks for standardState<T>classesonMounted,onBeforeUnmount,onUnmountedonActivated,onDeactivatedonDependenciesChanged,onAfterDependenciesChanged- Works with regular
StatefulWidget
-
ReactiveProviderStateMixin- EffectScope and reactivity forState<T>classes- Provides
setup()method for initialization - Creates
EffectScopefor automatic cleanup - Works with
watchEffect(),watch(),computed()
- Provides
Changed #
-
Code restructured into semantic folders:
src/extensions/- Extension methods (.watch())src/mixins/- Mixin classes (BindMixin,LifecycleHooks,*StateMixin)src/widgets/- Widget classes (ReactiveWidget,BindWidget,Observe,ObserveEffect)
-
Barrel file imports - All modules now have barrel files for cleaner imports
0.7.1 - 2025-12-23 #
Fixed #
- Documentation - Updated all docstring examples in source files to use the new
(context)parameter signature for lifecycle hooks.
0.7.0 - 2025-12-23 #
Changed (BREAKING) #
-
Lifecycle hooks now receive
BuildContext- All lifecycle callbacks (onMounted,onUpdated,onUnmounted, etc.) now receiveBuildContextas a parameter, enabling access to InheritedWidgets likeTheme,MediaQuery, andNavigator.Migration:
// Before onMounted(() => print('Mounted!')); // After onMounted((context) => print('Mounted!')); // Or if context not needed: onMounted((_) => print('Mounted!'));
Added #
-
Context access in lifecycle hooks - Developers can now access Flutter's InheritedWidget system directly:
onMounted((context) { final theme = Theme.of(context); final size = MediaQuery.of(context).size; }); onDependenciesChanged((context) { // React to theme/locale/media changes final brightness = Theme.of(context).brightness; });
0.6.0 - 2025-12-23 #
Added #
-
BindMixin- Decoupledbind()into a composable mixin- Can be used with custom widgets for flexible state persistence
- Similar pattern to
LifecycleHooksmixin
-
BindableElementMixin- State storage mixin for custom elements- Provides index-based state storage
- Handles bind index reset on widget recreation
-
BindableElement- Base element class for bind-supporting widgetsReactiveElementnow extendsBindableElement
-
BindWidget- Lightweight alternative toReactiveWidget- Provides
bind()and lifecycle hooks - No automatic reactivity in
build()(useObserve/ObserveEffect) - Simpler mental model with explicit reactivity control
- Provides
-
onDependenciesChanged- Lifecycle hook for InheritedWidget changes- Called when MediaQuery, Theme, Locale, etc. change
- Triggered before processing the change
-
onAfterDependenciesChanged- Lifecycle hook after dependency processing- Called after processing InheritedWidget changes
Changed #
-
ReactiveWidgetrefactored - Now composed of:BindMixin- forbind()APILifecycleHooks- for lifecycle callbacks- Automatic reactivity in
render()
-
Architecture - Modular, composable architecture allows:
ReactiveWidget= BindMixin + LifecycleHooks + auto-reactivityBindWidget= BindMixin + LifecycleHooks (no auto-reactivity)- Custom widgets can use mixins directly
0.5.2 - 2025-12-23 #
Fixed #
- Lifecycle callback accumulation - Fixed issue where
onMountedand other lifecycle callbacks would accumulate when the same widget instance was reused across navigations. Callbacks are now cleared on unmount. - Updated to redus ^0.4.4 - Includes fix for effects not being stopped when scope stops, preventing timer accumulation.
0.5.1 - 2025-12-23 #
Fixed #
- bind() type mismatch bug - Fixed
TypeErrorwhen usingbind()with multipleReftypes andwatch()insetup(). The issue occurred when fields were accessed in different phases (setup vs render), causing incorrect index assignment. Now correctly tracks widget instances to reset indices only on widget recreation.
0.5.0 - 2025-12-20 #
Added #
-
Observe<T>widget - Watches a reactive source and rebuilds when it changes- Takes a
sourcefunction (Ref, Computed, or getter) - Similar to
watch()but as a widget - Only rebuilds when source value changes
- Takes a
-
ObserveMultiple<T>widget - Watches multiple sources- Takes list of
sources - Rebuilds when any source changes
- Takes list of
-
ObserveEffectwidget - Auto-tracks reactive dependencies- Similar to
watchEffect()but as a widget - Tracks any
.valueaccess in builder - Most fine-grained reactivity option
- Similar to
Changed #
- Folder restructure: Renamed
component/towidget/ - File split: Split into individual files:
reactive_widget.dart- ReactiveWidget + ReactiveElementobserve.dart- Observe + ObserveMultiple widgetsobserve_effect.dart- ObserveEffect widgetlifecycle.dart- Lifecycle hooks mixin
0.4.0 - 2025-12-20 #
Added #
bind<T>()API - New simpler way to bind state to Elementlate final store = bind(() => MyStore())- Bind stores or any value- Index-based storage - no Symbol keys needed
- State persists across parent widget rebuilds
- Supports store pattern for encapsulated business logic
Removed #
state()andgetState()methods - Replaced bybind()
0.3.1 - 2025-12-19 #
Fixed #
- Updated README with new
ReactiveWidgetAPI and.watch(context)documentation
0.3.0 - 2025-12-19 #
Added #
- ReactiveWidget - New single-class component design
- .watch(context) Extension - Fine-grained reactivity for any widget
- DI Moved to redus_dart - Dependency injection from
package:redus/di.dart