From 541a88802806fc806af9f178eadd341f6e8971c8 Mon Sep 17 00:00:00 2001 From: Sergey Molchanovsky Date: Mon, 6 Nov 2023 14:07:06 +0300 Subject: [PATCH 1/9] Observer.optimized added --- flutter_mobx/CHANGELOG.md | 10 ++++++ flutter_mobx/lib/src/observer.dart | 54 ++++++++++++++++++------------ flutter_mobx/pubspec.yaml | 2 +- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/flutter_mobx/CHANGELOG.md b/flutter_mobx/CHANGELOG.md index 41a402e06..27fbbcd7c 100644 --- a/flutter_mobx/CHANGELOG.md +++ b/flutter_mobx/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.2.0 + - `Observer` is updated with new optional `child` parameter, so you can exclude child branch from re-render. - [@subzero911](https://github.com/subzero911) \ + You should provide either `builder` or both `builderWithChild` and `child`: + ```dart + Observer.optimized( + 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 88d48b41d..a4b7670b0 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,46 @@ class Observer extends StatelessObserverWidget String? name, bool? warnWhenNoObservables, }) : debugConstructingStackFrame = debugFindConstructingStackFrame(), + builderOptimized = null, + child = 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.optimized({ + Key? key, + required this.builderOptimized, + required this.child, + String? name, + bool? warnWhenNoObservables, + }) : debugConstructingStackFrame = debugFindConstructingStackFrame(), + builder = null, + super( + key: key, + name: name, + warnWhenNoObservables: warnWhenNoObservables, + ); + + final WidgetBuilder? builder; + + /// A builder that builds a widget given a child. + final TransitionBuilder? builderOptimized; + + /// 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' - : ''); + String getName() => super.getName() + (debugConstructingStackFrame != null ? '\n$debugConstructingStackFrame' : ''); @override - Widget build(BuildContext context) => builder(context); + Widget build(BuildContext context) => builderOptimized?.call(context, child) ?? builder!.call(context); /// Matches constructor stack frames, in both VM and web environments. static final _constructorStackFramePattern = RegExp(r'\bnew\b'); @@ -76,15 +91,10 @@ class Observer extends StatelessObserverWidget // regex) .skip(3) // Search for the first non-constructor frame - .firstWhere( - (frame) => !_constructorStackFramePattern.hasMatch(frame), - orElse: () => ''); - - final stackFrameCore = - _stackFrameCleanUpPattern.firstMatch(rawStackFrame)?.group(1); - final cleanedStackFrame = stackFrameCore == null - ? null - : 'Observer constructed from: $stackFrameCore'; + .firstWhere((frame) => !_constructorStackFramePattern.hasMatch(frame), orElse: () => ''); + + final stackFrameCore = _stackFrameCleanUpPattern.firstMatch(rawStackFrame)?.group(1); + final cleanedStackFrame = stackFrameCore == null ? null : 'Observer constructed from: $stackFrameCore'; stackFrame = cleanedStackFrame; } diff --git a/flutter_mobx/pubspec.yaml b/flutter_mobx/pubspec.yaml index f95ed157e..288d7d819 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 From 25d06dc8edf15656f3c7f2420ef679bed7fbf813 Mon Sep 17 00:00:00 2001 From: Sergey Molchanovsky Date: Mon, 6 Nov 2023 14:14:00 +0300 Subject: [PATCH 2/9] Test added --- flutter_mobx/test/flutter_mobx_test.dart | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/flutter_mobx/test/flutter_mobx_test.dart b/flutter_mobx/test/flutter_mobx_test.dart index 72d9baafb..504e8c5f9 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.optimized'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.optimized( + builderOptimized: (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); From 95e0936a9e4a27954752200cddbc2644b0373049 Mon Sep 17 00:00:00 2001 From: Sergey Molchanovsky Date: Mon, 6 Nov 2023 14:20:45 +0300 Subject: [PATCH 3/9] Changelog written --- flutter_mobx/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flutter_mobx/CHANGELOG.md b/flutter_mobx/CHANGELOG.md index 27fbbcd7c..7cd57f4ef 100644 --- a/flutter_mobx/CHANGELOG.md +++ b/flutter_mobx/CHANGELOG.md @@ -1,9 +1,9 @@ ## 2.2.0 - - `Observer` is updated with new optional `child` parameter, so you can exclude child branch from re-render. - [@subzero911](https://github.com/subzero911) \ - You should provide either `builder` or both `builderWithChild` and `child`: + - `Observer` is updated with the new `Observer.optimized` constructor, so you can exclude child branch from the re-rendering. - [@subzero911](https://github.com/subzero911) \ + In case if you use `Builder.optimized`, you should provide two parameters: `builderOptimized` and `child`: ```dart Observer.optimized( - builderWithChild: (context, child) => FooWidget(foo: foo, child: child), + builderOptimized: (context, child) => FooWidget(foo: foo, child: child), child: BarWidget(), // is not rebuilt ), ``` From fac9879a9699e7d7c864c87f427eab9ceb3ce5a4 Mon Sep 17 00:00:00 2001 From: Sergey Molchanovsky Date: Mon, 6 Nov 2023 14:20:58 +0300 Subject: [PATCH 4/9] asserts --- flutter_mobx/lib/src/observer.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flutter_mobx/lib/src/observer.dart b/flutter_mobx/lib/src/observer.dart index a4b7670b0..31c93541a 100644 --- a/flutter_mobx/lib/src/observer.dart +++ b/flutter_mobx/lib/src/observer.dart @@ -26,6 +26,7 @@ class Observer extends StatelessObserverWidget { }) : debugConstructingStackFrame = debugFindConstructingStackFrame(), builderOptimized = null, child = null, + assert(builder != null), super( key: key, name: name, @@ -42,6 +43,7 @@ class Observer extends StatelessObserverWidget { bool? warnWhenNoObservables, }) : debugConstructingStackFrame = debugFindConstructingStackFrame(), builder = null, + assert(builderOptimized != null && child != null), super( key: key, name: name, From 7adab86dc3c5b72ce0ad2a1005b5a600a4d7773b Mon Sep 17 00:00:00 2001 From: Sergey Molchanovsky Date: Mon, 6 Nov 2023 14:22:32 +0300 Subject: [PATCH 5/9] set_version --- flutter_mobx/lib/version.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_mobx/lib/version.dart b/flutter_mobx/lib/version.dart index 416e1234e..855f13a8b 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'; From 151416d60e54f224545feb437230f75d7161ffa8 Mon Sep 17 00:00:00 2001 From: Sergey Molchanovsky Date: Mon, 6 Nov 2023 14:44:18 +0300 Subject: [PATCH 6/9] fix comment --- flutter_mobx/lib/src/observer.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flutter_mobx/lib/src/observer.dart b/flutter_mobx/lib/src/observer.dart index 31c93541a..ce778c2cb 100644 --- a/flutter_mobx/lib/src/observer.dart +++ b/flutter_mobx/lib/src/observer.dart @@ -52,10 +52,9 @@ class Observer extends StatelessObserverWidget { final WidgetBuilder? builder; - /// A builder that builds a widget given a child. final TransitionBuilder? builderOptimized; - /// The child widget to pass to the [builderWithChild]. + /// The child widget to pass to the [builderOptimized]. final Widget? child; /// The stack frame pointing to the source that constructed this instance. From fc46ddc5853267fd95325cfd54cb61a0c03ba261 Mon Sep 17 00:00:00 2001 From: Sergey Molchanovsky Date: Tue, 7 Nov 2023 10:49:19 +0300 Subject: [PATCH 7/9] revert formatting --- flutter_mobx/lib/src/observer.dart | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/flutter_mobx/lib/src/observer.dart b/flutter_mobx/lib/src/observer.dart index ce778c2cb..b770ca074 100644 --- a/flutter_mobx/lib/src/observer.dart +++ b/flutter_mobx/lib/src/observer.dart @@ -61,7 +61,11 @@ class Observer extends StatelessObserverWidget { final String? debugConstructingStackFrame; @override - String getName() => super.getName() + (debugConstructingStackFrame != null ? '\n$debugConstructingStackFrame' : ''); + String getName() => + super.getName() + + (debugConstructingStackFrame != null + ? '\n$debugConstructingStackFrame' + : ''); @override Widget build(BuildContext context) => builderOptimized?.call(context, child) ?? builder!.call(context); @@ -92,10 +96,16 @@ class Observer extends StatelessObserverWidget { // regex) .skip(3) // Search for the first non-constructor frame - .firstWhere((frame) => !_constructorStackFramePattern.hasMatch(frame), orElse: () => ''); - - final stackFrameCore = _stackFrameCleanUpPattern.firstMatch(rawStackFrame)?.group(1); - final cleanedStackFrame = stackFrameCore == null ? null : 'Observer constructed from: $stackFrameCore'; + .firstWhere( + (frame) => + !_constructorStackFramePattern.hasMatch(frame), + orElse: () => ''); + + final stackFrameCore = + _stackFrameCleanUpPattern.firstMatch(rawStackFrame)?.group(1); + final cleanedStackFrame = stackFrameCore == null + ? null + : 'Observer constructed from: $stackFrameCore'; stackFrame = cleanedStackFrame; } From a854fa627097df05654202173c7afbdb9cf9732f Mon Sep 17 00:00:00 2001 From: Sergey Molchanovsky Date: Sun, 19 Nov 2023 13:16:25 +0300 Subject: [PATCH 8/9] renamed optimized to withChild --- flutter_mobx/CHANGELOG.md | 8 ++++---- flutter_mobx/lib/src/observer.dart | 14 +++++++------- flutter_mobx/test/flutter_mobx_test.dart | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/flutter_mobx/CHANGELOG.md b/flutter_mobx/CHANGELOG.md index 7cd57f4ef..32ec939f3 100644 --- a/flutter_mobx/CHANGELOG.md +++ b/flutter_mobx/CHANGELOG.md @@ -1,9 +1,9 @@ ## 2.2.0 - - `Observer` is updated with the new `Observer.optimized` constructor, so you can exclude child branch from the re-rendering. - [@subzero911](https://github.com/subzero911) \ - In case if you use `Builder.optimized`, you should provide two parameters: `builderOptimized` and `child`: + - `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.optimized( - builderOptimized: (context, child) => FooWidget(foo: foo, child: child), + Observer.withChild( + builderWithChild: (context, child) => FooWidget(foo: foo, child: child), child: BarWidget(), // is not rebuilt ), ``` diff --git a/flutter_mobx/lib/src/observer.dart b/flutter_mobx/lib/src/observer.dart index b770ca074..770f3b73d 100644 --- a/flutter_mobx/lib/src/observer.dart +++ b/flutter_mobx/lib/src/observer.dart @@ -24,7 +24,7 @@ class Observer extends StatelessObserverWidget { String? name, bool? warnWhenNoObservables, }) : debugConstructingStackFrame = debugFindConstructingStackFrame(), - builderOptimized = null, + builderWithChild = null, child = null, assert(builder != null), super( @@ -35,15 +35,15 @@ class Observer extends StatelessObserverWidget { /// Observer which excludes the child branch // ignore: prefer_const_constructors_in_immutables - Observer.optimized({ + Observer.withChild({ Key? key, - required this.builderOptimized, + required this.builderWithChild, required this.child, String? name, bool? warnWhenNoObservables, }) : debugConstructingStackFrame = debugFindConstructingStackFrame(), builder = null, - assert(builderOptimized != null && child != null), + assert(builderWithChild != null && child != null), super( key: key, name: name, @@ -52,9 +52,9 @@ class Observer extends StatelessObserverWidget { final WidgetBuilder? builder; - final TransitionBuilder? builderOptimized; + final TransitionBuilder? builderWithChild; - /// The child widget to pass to the [builderOptimized]. + /// The child widget to pass to the [builderWithChild]. final Widget? child; /// The stack frame pointing to the source that constructed this instance. @@ -68,7 +68,7 @@ class Observer extends StatelessObserverWidget { : ''); @override - Widget build(BuildContext context) => builderOptimized?.call(context, child) ?? builder!.call(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'); diff --git a/flutter_mobx/test/flutter_mobx_test.dart b/flutter_mobx/test/flutter_mobx_test.dart index 504e8c5f9..7a9c1a707 100644 --- a/flutter_mobx/test/flutter_mobx_test.dart +++ b/flutter_mobx/test/flutter_mobx_test.dart @@ -67,8 +67,8 @@ void main() { await tester.pumpWidget( MaterialApp( - home: Observer.optimized( - builderOptimized: (context, child) { + home: Observer.withChild( + builderWithChild: (context, child) { return Column( children: [ ElevatedButton(onPressed: () => message.value = 'Clicked', child: Container()), From bf2623fe72ba2e0f706f4d53f12ecb6c9f2de1a7 Mon Sep 17 00:00:00 2001 From: Sergey Molchanovsky Date: Sun, 19 Nov 2023 13:25:35 +0300 Subject: [PATCH 9/9] comment fix --- flutter_mobx/test/flutter_mobx_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_mobx/test/flutter_mobx_test.dart b/flutter_mobx/test/flutter_mobx_test.dart index 7a9c1a707..e8c8a6633 100644 --- a/flutter_mobx/test/flutter_mobx_test.dart +++ b/flutter_mobx/test/flutter_mobx_test.dart @@ -59,7 +59,7 @@ void main() { expect(renderCount, equals(1)); }); - testWidgets("Observer.optimized's child doesn't re-render", (tester) async { + testWidgets("Observer.withChild's child doesn't re-render", (tester) async { final message = Observable('Click'); final key1 = UniqueKey(); final key2 = UniqueKey();