diff --git a/flutter_mobx/CHANGELOG.md b/flutter_mobx/CHANGELOG.md index 41a402e0..32ec939f 100644 --- a/flutter_mobx/CHANGELOG.md +++ b/flutter_mobx/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.2.0 + - `Observer` is updated with the new `Observer.withChild` constructor, so you can exclude child branch from the re-rendering. - [@subzero911](https://github.com/subzero911) \ + In case if you use `Builder.withChild`, you should provide two parameters: `builderWithChild` and `child`: + ```dart + Observer.withChild( + builderWithChild: (context, child) => FooWidget(foo: foo, child: child), + child: BarWidget(), // is not rebuilt + ), + ``` + ## 2.1.1 - refactor: export `MultiReactionBuilder` from `flutter_mobx.dart` by [@amondnet](https://github.com/amondnet) diff --git a/flutter_mobx/lib/src/observer.dart b/flutter_mobx/lib/src/observer.dart index 88d48b41..770f3b73 100644 --- a/flutter_mobx/lib/src/observer.dart +++ b/flutter_mobx/lib/src/observer.dart @@ -16,11 +16,7 @@ bool debugAddStackTraceInObserverName = true; /// See also: /// /// - [Builder], which is the same thing but for [StatelessWidget] instead. -class Observer extends StatelessObserverWidget - // Implements Builder to import the documentation of `builder` - implements - // ignore: avoid_implementing_value_types - Builder { +class Observer extends StatelessObserverWidget { // ignore: prefer_const_constructors_in_immutables Observer({ Key? key, @@ -28,27 +24,51 @@ class Observer extends StatelessObserverWidget String? name, bool? warnWhenNoObservables, }) : debugConstructingStackFrame = debugFindConstructingStackFrame(), + builderWithChild = null, + child = null, + assert(builder != null), super( key: key, name: name, warnWhenNoObservables: warnWhenNoObservables, ); - @override - final WidgetBuilder builder; + /// Observer which excludes the child branch + // ignore: prefer_const_constructors_in_immutables + Observer.withChild({ + Key? key, + required this.builderWithChild, + required this.child, + String? name, + bool? warnWhenNoObservables, + }) : debugConstructingStackFrame = debugFindConstructingStackFrame(), + builder = null, + assert(builderWithChild != null && child != null), + super( + key: key, + name: name, + warnWhenNoObservables: warnWhenNoObservables, + ); + + final WidgetBuilder? builder; + + final TransitionBuilder? builderWithChild; + + /// The child widget to pass to the [builderWithChild]. + final Widget? child; /// The stack frame pointing to the source that constructed this instance. final String? debugConstructingStackFrame; @override String getName() => - super.getName() + - (debugConstructingStackFrame != null - ? '\n$debugConstructingStackFrame' - : ''); + super.getName() + + (debugConstructingStackFrame != null + ? '\n$debugConstructingStackFrame' + : ''); @override - Widget build(BuildContext context) => builder(context); + Widget build(BuildContext context) => builderWithChild?.call(context, child) ?? builder!.call(context); /// Matches constructor stack frames, in both VM and web environments. static final _constructorStackFramePattern = RegExp(r'\bnew\b'); @@ -77,14 +97,15 @@ class Observer extends StatelessObserverWidget .skip(3) // Search for the first non-constructor frame .firstWhere( - (frame) => !_constructorStackFramePattern.hasMatch(frame), + (frame) => + !_constructorStackFramePattern.hasMatch(frame), orElse: () => ''); final stackFrameCore = - _stackFrameCleanUpPattern.firstMatch(rawStackFrame)?.group(1); + _stackFrameCleanUpPattern.firstMatch(rawStackFrame)?.group(1); final cleanedStackFrame = stackFrameCore == null - ? null - : 'Observer constructed from: $stackFrameCore'; + ? null + : 'Observer constructed from: $stackFrameCore'; stackFrame = cleanedStackFrame; } diff --git a/flutter_mobx/lib/version.dart b/flutter_mobx/lib/version.dart index 416e1234..855f13a8 100644 --- a/flutter_mobx/lib/version.dart +++ b/flutter_mobx/lib/version.dart @@ -1,4 +1,4 @@ // Generated via set_version.dart. !!!DO NOT MODIFY BY HAND!!! /// The current version as per `pubspec.yaml`. -const version = '2.1.1'; +const version = '2.2.0'; diff --git a/flutter_mobx/pubspec.yaml b/flutter_mobx/pubspec.yaml index f95ed157..288d7d81 100644 --- a/flutter_mobx/pubspec.yaml +++ b/flutter_mobx/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_mobx description: Flutter integration for MobX. It provides a set of Observer widgets that automatically rebuild when the tracked observables change. -version: 2.1.1 +version: 2.2.0 homepage: https://github.com/mobxjs/mobx.dart issue_tracker: https://github.com/mobxjs/mobx.dart/issues diff --git a/flutter_mobx/test/flutter_mobx_test.dart b/flutter_mobx/test/flutter_mobx_test.dart index 72d9baaf..e8c8a663 100644 --- a/flutter_mobx/test/flutter_mobx_test.dart +++ b/flutter_mobx/test/flutter_mobx_test.dart @@ -59,6 +59,47 @@ void main() { expect(renderCount, equals(1)); }); + testWidgets("Observer.withChild's child doesn't re-render", (tester) async { + final message = Observable('Click'); + final key1 = UniqueKey(); + final key2 = UniqueKey(); + final key3 = UniqueKey(); + + await tester.pumpWidget( + MaterialApp( + home: Observer.withChild( + builderWithChild: (context, child) { + return Column( + children: [ + ElevatedButton(onPressed: () => message.value = 'Clicked', child: Container()), + Text(message.value, key: key1), + child!, + Builder( + builder: (context) { + return Text(message.value, key: key3); + } + ), + ], + ); + }, + child: Text(message.value, key: key2), + ), + ), + ); + + expect(tester.widget(find.byKey(key1)).data, equals('Click')); + expect(tester.widget(find.byKey(key2)).data, equals('Click')); + expect(tester.widget(find.byKey(key3)).data, equals('Click')); + + await tester.tap(find.byType(ElevatedButton)); + expect(message.value, equals('Clicked')); + + await tester.pump(); + expect(tester.widget(find.byKey(key1)).data, equals('Clicked')); // Observer rebuilt the Text1 + expect(tester.widget(find.byKey(key2)).data, equals('Click')); // child Text2 did not change + expect(tester.widget(find.byKey(key3)).data, equals('Clicked')); // Builder does not preserve from rebuild + }); + testWidgets('Observer build should call reaction.track', (tester) async { final mock = MockReaction(); when(() => mock.hasObservables).thenReturn(true);