roi peker

'group id' for GetBuilder, improved doc, big code refactor in Controller

- improved code docs for `BindingsBuilder` clarifying the issue with fat arrows and Get.put()
- major change in `GetxController`, `GetBuilder`, `SimpleBuilder` to improve docs, code cleanup (left a "clean" mess with the previous code to revert if needed)
- new `GetStateUpdaterMixin` and `GetStateUpdate` in favour of `StateSetter` (big change).
- Fixes issue #507, quick code for testing multiple GetBuilder ids can be found here: https://gist.github.com/roipeker/f34654ca35b80f5bf34736bd865f6358.
Warning: is a big change, but I did my tests and seems to be working fine. Please, test performance and potential bugs.
  1 +import 'dart:ui';
  2 +
1 import 'package:get/get.dart'; 3 import 'package:get/get.dart';
2 4
3 /// [Bindings] should be extended or implemented. 5 /// [Bindings] should be extended or implemented.
@@ -16,14 +18,20 @@ abstract class Bindings { @@ -16,14 +18,20 @@ abstract class Bindings {
16 /// GetPage( 18 /// GetPage(
17 /// name: '/', 19 /// name: '/',
18 /// page: () => Home(), 20 /// page: () => Home(),
19 -/// binding: BindingsBuilder(() => Get.put(HomeController())), 21 +/// // This might cause you an error.
  22 +/// // binding: BindingsBuilder(() => Get.put(HomeController())),
  23 +/// binding: BindingsBuilder(() { Get.put(HomeController(); })),
  24 +/// // Using .lazyPut() works fine.
  25 +/// // binding: BindingsBuilder(() => Get.lazyPut(() => HomeController())),
20 /// ), 26 /// ),
21 /// ``` 27 /// ```
22 class BindingsBuilder<T> extends Bindings { 28 class BindingsBuilder<T> extends Bindings {
23 /// Register your dependencies in the [builder] callback. 29 /// Register your dependencies in the [builder] callback.
24 - final void Function() builder; 30 + final VoidCallback builder;
25 31
26 - /// Shortcut to register 1 Controller with Get.put(). 32 + /// Shortcut to register 1 Controller with Get.put(),
  33 + /// Prevents the issue of the fat arrow function with the constructor.
  34 + /// BindingsBuilder(() => Get.put(HomeController())),
27 /// 35 ///
28 /// Sample: 36 /// Sample:
29 /// ``` 37 /// ```
@@ -39,6 +47,9 @@ class BindingsBuilder<T> extends Bindings { @@ -39,6 +47,9 @@ class BindingsBuilder<T> extends Bindings {
39 .put<T>(null, tag: tag, permanent: permanent, builder: builder)); 47 .put<T>(null, tag: tag, permanent: permanent, builder: builder));
40 } 48 }
41 49
  50 + /// WARNING: don't use `()=> Get.put(Controller())`,
  51 + /// if only passing 1 callback use `BindingsBuilder.put(Controller())`
  52 + /// or `BindingsBuilder(()=> Get.lazyPut(Controller()))`
42 BindingsBuilder(this.builder); 53 BindingsBuilder(this.builder);
43 54
44 @override 55 @override
@@ -8,39 +8,109 @@ import 'package:get/state_manager.dart'; @@ -8,39 +8,109 @@ import 'package:get/state_manager.dart';
8 8
9 import 'simple_builder.dart'; 9 import 'simple_builder.dart';
10 10
11 -typedef Disposer = void Function(); 11 +// Changed to VoidCallback.
  12 +//typedef Disposer = void Function();
  13 +
  14 +// replacing StateSetter, return if the Widget is mounted for extra validation.
  15 +typedef GetStateUpdate = bool Function();
  16 +
  17 +/// Complies with [GetStateUpdater]
  18 +///
  19 +/// This mixin's function represents a [GetStateUpdater], and might be used
  20 +/// by [GetBuilder()], [SimpleBuilder()] (or similar) to comply
  21 +/// with [GetStateUpdate] signature. REPLACING the [StateSetter].
  22 +/// Avoids the potential (but extremely unlikely) issue of having
  23 +/// the Widget in a dispose() state, and abstracts the API from the ugly fn((){}).
  24 +/// TODO: check performance HIT for the extra method call.
  25 +///
  26 +mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> {
  27 + bool getUpdate() {
  28 + final _mounted = mounted;
  29 + if (_mounted) setState(() {});
  30 + return _mounted;
  31 + }
  32 +}
12 33
13 class GetxController extends DisposableInterface { 34 class GetxController extends DisposableInterface {
14 - final HashSet<StateSetter> _updaters = HashSet<StateSetter>(); 35 + final _updaters = HashSet<GetStateUpdate>();
  36 +
  37 +// final _updatersIds = HashMap<String, StateSetter>(); //<old>
  38 + final _updatersIds = HashMap<String, GetStateUpdate>();
  39 +
  40 +// final _updatersGroupIds = HashMap<String, List>(); //<old>
  41 + final _updatersGroupIds = HashMap<String, HashMap<int, GetStateUpdate>>();
15 42
16 - final HashMap<String, StateSetter> _updatersIds =  
17 - HashMap<String, StateSetter>(); 43 + static int _groupIdCount = 0;
18 44
19 - /// Update GetBuilder with update(); 45 + /// Rebuilds [GetBuilder] each time you call [update()];
  46 + /// Can take a List of [ids], that will only update the matching
  47 + /// `GetBuilder( id: )`,
  48 + /// [ids] can be reused among `GetBuilders` like group tags.
  49 + /// The update will only notify the Widgets, if [condition] is true.
20 void update([List<String> ids, bool condition = true]) { 50 void update([List<String> ids, bool condition = true]) {
21 if (!condition) return; 51 if (!condition) return;
22 - (ids == null)  
23 - ? _updaters.forEach((rs) => rs(() {}))  
24 - : ids.forEach((element) {  
25 - _updatersIds[element]?.call(() {});  
26 - }); 52 + if (ids == null) {
  53 +// _updaters.forEach((rs) => rs(() {}));//<old>
  54 + _updaters.forEach((rs) => rs());
  55 + } else {
  56 + ids.forEach((element) {
  57 +// _updatersGroupIds[element]?.forEach((k, rs) => rs(() {}));//<old>
  58 +// _updatersIds[element]?.call(() {});//<old>
  59 + _updatersIds[element]?.call();
  60 + _updatersGroupIds[element]?.forEach((k, rs) => rs());
  61 + });
  62 + }
27 } 63 }
28 64
29 - Disposer addListener(StateSetter listener) { 65 +// VoidCallback addListener(StateSetter listener) {//<old>
  66 + VoidCallback addListener(GetStateUpdate listener) {
30 _updaters.add(listener); 67 _updaters.add(listener);
31 return () => _updaters.remove(listener); 68 return () => _updaters.remove(listener);
32 } 69 }
33 70
34 - // void removeListener(StateSetter listener) {  
35 - // _updaters.remove(listener);  
36 - // } 71 +// VoidCallback addListenerId(String key, StateSetter listener) {//<old>
  72 + VoidCallback addListenerId(String key, GetStateUpdate listener) {
  73 +// _printCurrentIds();
  74 + if (_updatersIds.containsKey(key)) {
  75 + final _innerKey = _groupIdCount++;
  76 +// final _ref = _updatersGroupIds[key] ??= HashMap<int, StateSetter>();//<old>
  77 + final _ref = _updatersGroupIds[key] ??= HashMap<int, GetStateUpdate>();
  78 + _ref[_innerKey] = listener;
  79 + return () {
  80 + _ref?.remove(_innerKey);
  81 +// _printCurrentIds();
  82 + };
  83 + } else {
  84 + _updatersIds[key] = listener;
  85 + return () => _updatersIds.remove(key);
  86 + }
  87 + }
37 88
38 - Disposer addListenerId(String key, StateSetter listener) {  
39 - _updatersIds[key] = listener;  
40 - return () => _updatersIds.remove(key); 89 + /// To dispose an [id] from future updates(), this ids are registered
  90 + /// by [GetBuilder()] or similar, so is a way to unlink the state change with
  91 + /// the Widget from the Controller.
  92 + void disposeId(String id) {
  93 + _updatersIds.remove(id);
  94 + _updatersGroupIds.remove(id);
41 } 95 }
42 96
43 - void disposeKey(String key) => _updatersIds.remove(key); 97 + /// Remove this after checking the new implementation makes sense.
  98 + /// Uncomment this if you wanna control the removal of ids..
  99 +// bool _debugging = false;
  100 +// Future<void> _printCurrentIds() async {
  101 +// if (_debugging) return;
  102 +// _debugging = true;
  103 +// print('about to debug...');
  104 +// await Future.delayed(Duration(milliseconds: 10));
  105 +// int totalGroups = 0;
  106 +// _updatersGroupIds.forEach((key, value) {
  107 +// totalGroups += value.length;
  108 +// });
  109 +// int totalIds = _updatersIds.length;
  110 +// print(
  111 +// 'Total: ${totalIds + totalGroups}, in groups:$totalGroups, solo ids:$totalIds');
  112 +// _debugging = false;
  113 +// }
44 } 114 }
45 115
46 class GetBuilder<T extends GetxController> extends StatefulWidget { 116 class GetBuilder<T extends GetxController> extends StatefulWidget {
@@ -53,6 +123,7 @@ class GetBuilder<T extends GetxController> extends StatefulWidget { @@ -53,6 +123,7 @@ class GetBuilder<T extends GetxController> extends StatefulWidget {
53 final void Function(State state) initState, dispose, didChangeDependencies; 123 final void Function(State state) initState, dispose, didChangeDependencies;
54 final void Function(GetBuilder oldWidget, State state) didUpdateWidget; 124 final void Function(GetBuilder oldWidget, State state) didUpdateWidget;
55 final T init; 125 final T init;
  126 +
56 const GetBuilder({ 127 const GetBuilder({
57 Key key, 128 Key key,
58 this.init, 129 this.init,
@@ -68,15 +139,20 @@ class GetBuilder<T extends GetxController> extends StatefulWidget { @@ -68,15 +139,20 @@ class GetBuilder<T extends GetxController> extends StatefulWidget {
68 this.didUpdateWidget, 139 this.didUpdateWidget,
69 }) : assert(builder != null), 140 }) : assert(builder != null),
70 super(key: key); 141 super(key: key);
  142 +
71 @override 143 @override
72 _GetBuilderState<T> createState() => _GetBuilderState<T>(); 144 _GetBuilderState<T> createState() => _GetBuilderState<T>();
73 } 145 }
74 146
75 -class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> { 147 +class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
  148 + with GetStateUpdaterMixin {
76 GetxController controller; 149 GetxController controller;
77 bool isCreator = false; 150 bool isCreator = false;
78 - final HashSet<Disposer> disposers = HashSet<Disposer>();  
79 - Disposer remove; 151 +
  152 + /// TODO: @jonny, you intend to use disposers?
  153 +// final HashSet<Disposer> disposers = HashSet<Disposer>();
  154 +
  155 + VoidCallback remove;
80 156
81 @override 157 @override
82 void initState() { 158 void initState() {
@@ -110,28 +186,42 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> { @@ -110,28 +186,42 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> {
110 GetConfig.smartManagement == SmartManagement.onlyBuilder) { 186 GetConfig.smartManagement == SmartManagement.onlyBuilder) {
111 controller?.onStart(); 187 controller?.onStart();
112 } 188 }
  189 + _subscribeToController();
  190 + }
  191 +
  192 + /// Register to listen Controller's events.
  193 + /// It gets a reference to the remove() callback, to delete the
  194 + /// setState "link" from the Controller.
  195 + void _subscribeToController() {
  196 + remove?.call();
113 remove = (widget.id == null) 197 remove = (widget.id == null)
114 - ? controller?.addListener(setState)  
115 - : controller?.addListenerId(widget.id, setState); 198 +// ? controller?.addListener(setState)//<old>
  199 +// : controller?.addListenerId(widget.id, setState);//<old>
  200 + ? controller?.addListener(getUpdate)
  201 + : controller?.addListenerId(widget.id, getUpdate);
116 } 202 }
117 203
  204 + /// Sample for [GetStateUpdate] when you don't wanna use [GetStateHelper mixin].
  205 +// bool _getUpdater() {
  206 +// final _mounted = mounted;
  207 +// if (_mounted) setState(() {});
  208 +// return _mounted;
  209 +// }
  210 +
118 @override 211 @override
119 void dispose() { 212 void dispose() {
120 super.dispose(); 213 super.dispose();
121 if (widget.dispose != null) widget.dispose(this); 214 if (widget.dispose != null) widget.dispose(this);
122 if (isCreator || widget.assignId) { 215 if (isCreator || widget.assignId) {
123 if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) { 216 if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
124 - if (remove != null) remove();  
125 -  
126 GetInstance().delete<T>(tag: widget.tag); 217 GetInstance().delete<T>(tag: widget.tag);
127 } 218 }
128 - } else {  
129 - if (remove != null) remove();  
130 } 219 }
  220 + remove?.call();
131 221
132 - disposers.forEach((element) {  
133 - element();  
134 - }); 222 +// disposers.forEach((element) {
  223 +// element();
  224 +// });
135 } 225 }
136 226
137 @override 227 @override
@@ -145,6 +235,10 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> { @@ -145,6 +235,10 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> {
145 @override 235 @override
146 void didUpdateWidget(GetBuilder oldWidget) { 236 void didUpdateWidget(GetBuilder oldWidget) {
147 super.didUpdateWidget(oldWidget as GetBuilder<T>); 237 super.didUpdateWidget(oldWidget as GetBuilder<T>);
  238 + // to avoid conflicts when modifying a "grouped" id list.
  239 + if (oldWidget.id != widget.id) {
  240 + _subscribeToController();
  241 + }
148 if (widget.didUpdateWidget != null) widget.didUpdateWidget(oldWidget, this); 242 if (widget.didUpdateWidget != null) widget.didUpdateWidget(oldWidget, this);
149 } 243 }
150 244
@@ -85,8 +85,9 @@ class SimpleBuilder extends StatefulWidget { @@ -85,8 +85,9 @@ class SimpleBuilder extends StatefulWidget {
85 _SimpleBuilderState createState() => _SimpleBuilderState(); 85 _SimpleBuilderState createState() => _SimpleBuilderState();
86 } 86 }
87 87
88 -class _SimpleBuilderState extends State<SimpleBuilder> {  
89 - final HashSet<Disposer> disposers = HashSet<Disposer>(); 88 +class _SimpleBuilderState extends State<SimpleBuilder>
  89 + with GetStateUpdaterMixin {
  90 + final HashSet<VoidCallback> disposers = HashSet<VoidCallback>();
90 91
91 @override 92 @override
92 void dispose() { 93 void dispose() {
@@ -97,7 +98,7 @@ class _SimpleBuilderState extends State<SimpleBuilder> { @@ -97,7 +98,7 @@ class _SimpleBuilderState extends State<SimpleBuilder> {
97 @override 98 @override
98 Widget build(BuildContext context) { 99 Widget build(BuildContext context) {
99 return TaskManager.instance 100 return TaskManager.instance
100 - .exchange(disposers, setState, widget.builder, context); 101 + .exchange(disposers, getUpdate, widget.builder, context);
101 } 102 }
102 } 103 }
103 104
@@ -106,10 +107,13 @@ class TaskManager { @@ -106,10 +107,13 @@ class TaskManager {
106 static TaskManager _instance; 107 static TaskManager _instance;
107 static TaskManager get instance => _instance ??= TaskManager._(); 108 static TaskManager get instance => _instance ??= TaskManager._();
108 109
109 - StateSetter _setter;  
110 - HashSet<Disposer> _remove; 110 +// StateSetter _setter;//<old>
  111 + GetStateUpdate _setter;
111 112
112 - notify(HashSet<StateSetter> _updaters) { 113 + HashSet<VoidCallback> _remove;
  114 +
  115 +// void notify(HashSet<StateSetter> _updaters) { //<old>
  116 + void notify(HashSet<GetStateUpdate> _updaters) {
113 if (_setter != null) { 117 if (_setter != null) {
114 if (!_updaters.contains(_setter)) { 118 if (!_updaters.contains(_setter)) {
115 _updaters.add(_setter); 119 _updaters.add(_setter);
@@ -119,8 +123,9 @@ class TaskManager { @@ -119,8 +123,9 @@ class TaskManager {
119 } 123 }
120 124
121 Widget exchange( 125 Widget exchange(
122 - HashSet<Disposer> disposers,  
123 - StateSetter setState, 126 + HashSet<VoidCallback> disposers,
  127 +// StateSetter setState, //<old>
  128 + GetStateUpdate setState,
124 Widget Function(BuildContext) builder, 129 Widget Function(BuildContext) builder,
125 BuildContext context, 130 BuildContext context,
126 ) { 131 ) {