Jonny Borges
Committed by GitHub

Merge pull request #565 from roipeker/master

improve docs, cleanup workers, added samples, fixed rx operators
@@ -59,55 +59,101 @@ var name = 'Jonatas Borges'.obs; @@ -59,55 +59,101 @@ var name = 'Jonatas Borges'.obs;
59 59
60 That's all. It's *that* simple. 60 That's all. It's *that* simple.
61 61
62 -What did we do under the hood? We created a stream of Strings, assigned the initial value "Jonatas Borges", we warn all widgets that use "Jonatas Borges" that they now belong to this variable, and when it is changed, they will be changed too. This is the magic of Get, that only dart allows us to do this. 62 +From now on, we might refer to this reactive-".obs"(ervables) variables as _Rx_.
63 63
64 -Okay, but as we know, a widget can only be changed if it is inside a function, because static classes do not have the power to "auto-change". You will need to create a StreamBuilder, subscribe to listen to this variable, and create a "cascade" of StreamBuilder if you want to change several variables in the same scope, right?  
65 -No, you don't need a StreamBuilder, but you are right about static classes. 64 +What did we do under the hood? We created a `Stream` of `String`s, assigned the initial value `"Jonatas Borges"`, we notified all widgets that use `"Jonatas Borges"` that they now "belong" to this variable, and when the _Rx_ value changes, they will have to change as well.
66 65
67 -Well, in the view we usually have a lot of boilerplate when we want to change a specific widget. With Get you can also forget about this Boilerplate. StreamBuilder? initialValue? builder?  
68 -No, you just need to play this variable inside an Obx widget. 66 +This is the **magic of GetX**, thanks to Dart's capabilities.
  67 +
  68 +But, as we know, a `Widget` can only be changed if it is inside a function, because static classes do not have the power to "auto-change".
  69 +
  70 +You will need to create a `StreamBuilder`, subscribe to this variable to listen for changes, and create a "cascade" of nested `StreamBuilder` if you want to change several variables in the same scope, right?
  71 +
  72 +No, you don't need a `StreamBuilder`, but you are right about static classes.
  73 +
  74 +Well, in the view, we usually have a lot of boilerplate when we want to change a specific Widget, that's the Flutter way.
  75 +With **GetX** you can also forget about this boilerplate code.
  76 +
  77 +`StreamBuilder( … )`? `initialValue: …`? `builder: …`? Nope, you just need to place this variable inside an `Obx()` Widget.
69 78
70 ```dart 79 ```dart
71 Obx (() => Text (controller.name)); 80 Obx (() => Text (controller.name));
72 ``` 81 ```
73 82
74 -What do you need to memorize? Only `Obx(() =>`. You are just passing that widget through an arrow-function into an Obx. Obx is smart, and will only be changed if the value of name is changed. If name is "John" and you change it to "John", it will not have any changes on the screen, and Obx will simply ignore this change, and will not rebuild the widget, to save resources. Isn't that amazing? 83 +_What do you need to memorize?_ Only `Obx(() =>`.
  84 +
  85 +You are just passing that Widget through an arrow-function into an `Obx()` (the "Observer" of the _Rx_).
  86 +
  87 +`Obx` is pretty smart, and will only change if the value of `controller.name` changes.
  88 +
  89 +If `name` is `"John"`, and you change it to `"John"` (`name.value = "John"`), as it's the same `value` as before, nothing will change on the screen, and `Obx`, to save resources, will simply ignore the new value and not rebuild the Widget. **Isn't that amazing?**
75 90
76 -What if I have 5 observable variables within an Obx? It will update when any of them are changed. And if I have 30 variables in a class, when I update one, will it update all variables that are in that class? No, just the specific widget that uses that variable. 91 +> So, what if I have 5 _Rx_ (observable) variables within an `Obx`?
77 92
78 -GetX only updates the screen when the variable changes on the screen, if the screen remains the same, it will not reconstruct anything. 93 +It will just update when **any** of them changes.
79 94
  95 +> And if I have 30 variables in a class, when I update one, will it update **all** the variables that are in that class?
  96 +
  97 +Nope, just the **specific Widget** that uses that _Rx_ variable.
  98 +
  99 +So, **GetX** only updates the screen, when the _Rx_ variable changes it's value.
  100 +
  101 +```
  102 +final isOpen = false.obs;
  103 +
  104 +// NOTHING will happen... same value.
  105 +void onButtonTap() => isOpen.value=false;
  106 +```
80 ### Advantages 107 ### Advantages
81 108
82 -GetX is focused for when you need granular control over what is being updated. Is for situations where you want only the widget when a certain variable has been changed to be rebuilt. 109 +**GetX()** helps you when you need **granular** control over what's being updated.
  110 +
  111 +If you do not need `unique IDs`, because all your variables will be modified when you perform an action, then use `GetBuilder`,
  112 +because it's a Simple State Updater (in blocks, like `setState()`), made in just a few lines of code.
  113 +It was made simple, to have the least CPU impact, and just to fulfill a single purpose (a _State_ rebuild) and spend the minimum resources possible.
83 114
84 -If you do not need unique IDs, because all your variables will be changed when you perform an action, use GetBuilder, because it is a simple state updater in blocks, made in a few lines of code. It was done in a simple way, to have the least computational logic involved, just to fulfill a single purpose and spend the minimum resources possible for that purpose. 115 +If you need a **powerful** State Manager, you can't go wrong with **GetX**.
85 116
86 -If you want a powerful state manager, you can go without fear to GetX. It does not work with variables, but flows, everything in it is streams under the hood. You can use rxDart in conjunction with it, because everything is stream, you can hear the event of each "variable", because everything in it is stream, it is literally BLoC, easier than MobX, and without code generator or decorations . 117 +It doesn't work with variables, but __flows__, everything in it are `Streams` under the hood.
  118 +You can use _rxDart_ in conjunction with it, because everything are `Streams`,
  119 +you can listen the `event` of each "_Rx_ variable",
  120 +because everything in it are `Streams`.
87 121
88 -Without decorations, you can turn anything into Observable with just a ".obs". 122 +It is literally a _BLoC_ approach, easier than _MobX_, and without code generators or decorations.
  123 +You can turn **anything** into an _"Observable"_ with just a `.obs`.
89 124
90 -Maximum performance: In addition to having a smart algorithm for minimal reconstruction, Get uses comparators to make sure the state has changed. If you experience any errors in your application, and send a duplicate change of state, Get will ensure that your application does not collapse. 125 +### Maximum performance:
91 126
92 -The state only changes if the values ​​change. That's the main difference between Get, and using Computed from MobX for example. When joining two observables, when one is changed, the hearing of that observable will change. With Get, if you join two variables (which is unnecessary computed for that), GetX (similar to Observer) will only change if it implies a real change of state. 127 +In addition to having a smart algorithm for minimal rebuilds, **GetX** uses comparators
  128 +to make sure the State has changed.
  129 +
  130 +If you experience any errors in your app, and send a duplicate change of State,
  131 +**GetX** will ensure it will not crash.
  132 +
  133 +With **GetX** the State only changes if the `value` change.
  134 +That's the main difference between **GetX**, and using _`computed` from MobX_.
  135 +When joining two __observables__, and one changes; the listener of that _observable_ will change as well.
  136 +
  137 +With **GetX**, if you join two variables, `GetX()` (similar to `Observer()`) will only rebuild if it implies a real change of State.
93 138
94 ### Declaring a reactive variable 139 ### Declaring a reactive variable
95 140
96 -You have 3 ways to turn a variable into an observable.  
97 -The first is using Rx{Type}. 141 +You have 3 ways to turn a variable into an "observable".
  142 +
  143 +
  144 +1 - The first is using **`Rx{Type}`**.
98 145
99 ```dart 146 ```dart
100 -// initial value is recommended but not mandatory 147 +// initial value is recommended, but not mandatory
101 final name = RxString(''); 148 final name = RxString('');
102 final isLogged = RxBool(false); 149 final isLogged = RxBool(false);
103 final count = RxInt(0); 150 final count = RxInt(0);
104 final balance = RxDouble(0.0); 151 final balance = RxDouble(0.0);
105 -final number = RxNum(0)  
106 final items = RxList<String>([]); 152 final items = RxList<String>([]);
107 final myMap = RxMap<String, int>({}); 153 final myMap = RxMap<String, int>({});
108 ``` 154 ```
109 155
110 -The second is to use Rx and type it with `Rx<Type>` 156 +2 - The second is to use **`Rx`** and use Darts Generics, `Rx<Type>`
111 157
112 ```dart 158 ```dart
113 final name = Rx<String>(''); 159 final name = Rx<String>('');
@@ -122,7 +168,7 @@ final myMap = Rx<Map<String, int>>({}); @@ -122,7 +168,7 @@ final myMap = Rx<Map<String, int>>({});
122 final user = Rx<User>(); 168 final user = Rx<User>();
123 ``` 169 ```
124 170
125 -The third, more practical and easier approach, is just to add an .obs to your variable. 171 +3 - The third, more practical, easier and preferred approach, just add **`.obs`** as a property of your `value`:
126 172
127 ```dart 173 ```dart
128 final name = ''.obs; 174 final name = ''.obs;
@@ -137,9 +183,16 @@ final myMap = <String, int>{}.obs; @@ -137,9 +183,16 @@ final myMap = <String, int>{}.obs;
137 final user = User().obs; 183 final user = User().obs;
138 ``` 184 ```
139 185
140 -As we know, Dart is now heading towards null safety. Since that it is a good idea, from now on, you should start to use your variables always with an initial value. Transforming a variable into an observable with an initial value with Get is the simplest and most practical approach. You will literally add a ".obs" to the end of your variable, and that’s it, you’ve made it observable, and its value will be the initial value. 186 +##### Having a reactive state, is easy.
  187 +
  188 +As we know, _Dart_ is now heading towards _null safety_.
  189 +To be prepared, from now on, you should always start your _Rx_ variables with an **initial value**.
  190 +
  191 +> Transforming a variable into an _observable_ + _initial value_ with **GetX** is the simplest, and most practical approach.
  192 +
  193 +You will literally add a "`.obs`" to the end of your variable, and **that’s it**, you’ve made it observable,
  194 +and its `.value`, well, will be the _initial value_).
141 195
142 -You can add variables, and if you want to type your widget to get your controller inside, you just need to use GetX widget instead of Obx  
143 196
144 ### Using the values in the view 197 ### Using the values in the view
145 198
@@ -172,22 +225,31 @@ GetX<Controller>( @@ -172,22 +225,31 @@ GetX<Controller>(
172 ), 225 ),
173 ``` 226 ```
174 227
175 -If we increment the number of count 1, only count 1 and count 3 are reconstructed, because count 1 now has a value of 1, and 1 + 0 = 1, changing the sum value. 228 +If we increment `count1.value++`, it will print:
  229 +- `count 1 rebuild`
  230 +- `count 3 rebuild`
176 231
177 -If we change count 2, only count2 and 3 are reconstructed, because the value of 2 has changed, and the result of the sum is now 2. 232 +because `count1` has a value of `1`, and `1 + 0 = 1`, changing the `sum` getter value.
178 233
179 -If we add the number 1 to count 1, which already contains 1, no widget is reconstructed. If we add a value of 1 for count 1 and a value of 2 for count 2, only 2 and 3 will be reconstructed, simply because GetX not only changes what is necessary, it avoids duplicating events. 234 +If we change `count2.value++`, it will print:
  235 +- `count 2 rebuild`
  236 +- `count 3 rebuild`
  237 +
  238 +because `count2.value` changed, and the result of the `sum` is now `2`.
  239 +
  240 +- NOTE: By default, the very first event will rebuild the widget, even if it is the same `value`.
  241 + This behavior exists due to Boolean variables.
180 242
181 -- NOTE: By default, the first event will allow rebuild even if it is the same. We created this behavior due to dualistic variables, such as Boolean.  
182 Imagine you did this: 243 Imagine you did this:
183 244
184 ```dart 245 ```dart
185 var isLogged = false.obs; 246 var isLogged = false.obs;
186 ``` 247 ```
187 248
188 -and then you check if a user is logged in to trigger an event in "ever". 249 +And then, you checked if a user is "logged in" to trigger an event in `ever`.
189 250
190 ```dart 251 ```dart
  252 +@override
191 onInit(){ 253 onInit(){
192 ever(isLogged, fireRoute); 254 ever(isLogged, fireRoute);
193 isLogged.value = await Preferences.hasToken(); 255 isLogged.value = await Preferences.hasToken();
@@ -202,7 +264,10 @@ fireRoute(logged) { @@ -202,7 +264,10 @@ fireRoute(logged) {
202 } 264 }
203 ``` 265 ```
204 266
205 -if hasToken was false, there would be no change to isLogged, so ever would never be called. To avoid this type of behavior, the first change to an observable will always trigger an event, even if it is the same. 267 +if `hasToken` was `false`, there would be no change to `isLogged`, so `ever()` would never be called.
  268 +To avoid this type of behavior, the first change to an _observable_ will always trigger an event,
  269 +even if it contains the same `.value`.
  270 +
206 You can remove this behavior if you want, using: 271 You can remove this behavior if you want, using:
207 `isLogged.firstRebuild = false;` 272 `isLogged.firstRebuild = false;`
208 273
@@ -311,13 +376,13 @@ The "assignAll" api will clear the existing list and add any iterable objects th @@ -311,13 +376,13 @@ The "assignAll" api will clear the existing list and add any iterable objects th
311 376
312 ### Why i have to use .value 377 ### Why i have to use .value
313 378
314 -We could remove the obligation to use 'value' to String and int with a simple decoration and code generator, but the purpose of this lib is precisely not to need any external dependency. It is to offer an environment ready for programming, involving the essentials (management of routes, dependencies and states), in a simple, light and performance way without needing any external package. 379 +We could remove the obligation to use 'value' to `String` and `int` with a simple decoration and code generator, but the purpose of this library is precisely avoid external dependencies. We want to offer an environment ready for programming, involving the essentials (management of routes, dependencies and states), in a simple, lightweight and performant way, without a need of an external package.
315 380
316 You can literally add 3 letters to your pubspec (get) and a colon and start programming. All solutions included by default, from route management to state management, aim at ease, productivity and performance. 381 You can literally add 3 letters to your pubspec (get) and a colon and start programming. All solutions included by default, from route management to state management, aim at ease, productivity and performance.
317 382
318 The total weight of this library is less than that of a single state manager, even though it is a complete solution, and that is what you must understand. 383 The total weight of this library is less than that of a single state manager, even though it is a complete solution, and that is what you must understand.
319 384
320 -If you are bothered by value, and like a code generator, MobX is a great alternative, and you can use it in conjunction with Get. For those who want to add a single dependency in pubspec and start programming without worrying about the version of a package being incompatible with another, or if the error of a state update is coming from the state manager or dependency, or still, do not want to worrying about the availability of controllers, whether literally "just programming", get is just perfect. 385 +If you are bothered by `.value`, and like a code generator, MobX is a great alternative, and you can use it in conjunction with Get. For those who want to add a single dependency in pubspec and start programming without worrying about the version of a package being incompatible with another, or if the error of a state update is coming from the state manager or dependency, or still, do not want to worrying about the availability of controllers, whether literally "just programming", get is just perfect.
321 386
322 If you have no problem with the MobX code generator, or have no problem with the BLoC boilerplate, you can simply use Get for routes, and forget that it has state manager. Get SEM and RSM were born out of necessity, my company had a project with more than 90 controllers, and the code generator simply took more than 30 minutes to complete its tasks after a Flutter Clean on a reasonably good machine, if your project it has 5, 10, 15 controllers, any state manager will supply you well. If you have an absurdly large project, and code generator is a problem for you, you have been awarded this solution. 387 If you have no problem with the MobX code generator, or have no problem with the BLoC boilerplate, you can simply use Get for routes, and forget that it has state manager. Get SEM and RSM were born out of necessity, my company had a project with more than 90 controllers, and the code generator simply took more than 30 minutes to complete its tasks after a Flutter Clean on a reasonably good machine, if your project it has 5, 10, 15 controllers, any state manager will supply you well. If you have an absurdly large project, and code generator is a problem for you, you have been awarded this solution.
323 388
@@ -333,7 +398,7 @@ Obviously, if you don't use a type, you will need to have an instance of your co @@ -333,7 +398,7 @@ Obviously, if you don't use a type, you will need to have an instance of your co
333 Workers will assist you, triggering specific callbacks when an event occurs. 398 Workers will assist you, triggering specific callbacks when an event occurs.
334 399
335 ```dart 400 ```dart
336 -/// Called every time the variable $_ is changed 401 +/// Called every time `count1` changes.
337 ever(count1, (_) => print("$_ has been changed")); 402 ever(count1, (_) => print("$_ has been changed"));
338 403
339 /// Called only first time the variable $_ is changed 404 /// Called only first time the variable $_ is changed
@@ -345,17 +410,25 @@ debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); @@ -345,17 +410,25 @@ debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
345 /// Ignore all changes within 1 second. 410 /// Ignore all changes within 1 second.
346 interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); 411 interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
347 ``` 412 ```
  413 +All workers (except `debounce`) have a `condition` named parameter, which can be a `bool` or a callback that returns a `bool`.
  414 +This `condition` defines when the `callback` function executes.
  415 +
  416 +All workers returns a `Worker` instance, that you can use to cancel ( via `dispose()` ) the worker.
  417 +
  418 +- **`ever`**
  419 + is called every time the _Rx_ variable emits a new value.
  420 +
  421 +- **`everAll`**
  422 + Much like `ever`, but it takes a `List` of _Rx_ values Called every time its variable is changed. That's it.
348 423
349 -- ever  
350 -'ever' is called every time its variable is changed. That's it.  
351 424
352 -- ever 425 +- **`once`**
353 'once' is called only the first time the variable has been changed. 426 'once' is called only the first time the variable has been changed.
354 427
355 -- debounce 428 +- **`debounce`**
356 'debounce' is very useful in search functions, where you only want the API to be called when the user finishes typing. If the user types "Jonny", you will have 5 searches in the APIs, by the letter J, o, n, n, and y. With Get this does not happen, because you will have a "debounce" Worker that will only be triggered at the end of typing. 429 'debounce' is very useful in search functions, where you only want the API to be called when the user finishes typing. If the user types "Jonny", you will have 5 searches in the APIs, by the letter J, o, n, n, and y. With Get this does not happen, because you will have a "debounce" Worker that will only be triggered at the end of typing.
357 430
358 -- interval 431 +- **`interval`**
359 'interval' is different from the debouce. debouce if the user makes 1000 changes to a variable within 1 second, he will send only the last one after the stipulated timer (the default is 800 milliseconds). Interval will instead ignore all user actions for the stipulated period. If you send events for 1 minute, 1000 per second, debounce will only send you the last one, when the user stops strafing events. interval will deliver events every second, and if set to 3 seconds, it will deliver 20 events that minute. This is recommended to avoid abuse, in functions where the user can quickly click on something and get some advantage (imagine that the user can earn coins by clicking on something, if he clicked 300 times in the same minute, he would have 300 coins, using interval, you you can set a time frame for 3 seconds, and even then clicking 300 or a thousand times, the maximum he would get in 1 minute would be 20 coins, clicking 300 or 1 million times). The debounce is suitable for anti-DDos, for functions like search where each change to onChange would cause a query to your api. Debounce will wait for the user to stop typing the name, to make the request. If it were used in the coin scenario mentioned above, the user would only win 1 coin, because it is only executed, when the user "pauses" for the established time. 432 'interval' is different from the debouce. debouce if the user makes 1000 changes to a variable within 1 second, he will send only the last one after the stipulated timer (the default is 800 milliseconds). Interval will instead ignore all user actions for the stipulated period. If you send events for 1 minute, 1000 per second, debounce will only send you the last one, when the user stops strafing events. interval will deliver events every second, and if set to 3 seconds, it will deliver 20 events that minute. This is recommended to avoid abuse, in functions where the user can quickly click on something and get some advantage (imagine that the user can earn coins by clicking on something, if he clicked 300 times in the same minute, he would have 300 coins, using interval, you you can set a time frame for 3 seconds, and even then clicking 300 or a thousand times, the maximum he would get in 1 minute would be 20 coins, clicking 300 or 1 million times). The debounce is suitable for anti-DDos, for functions like search where each change to onChange would cause a query to your api. Debounce will wait for the user to stop typing the name, to make the request. If it were used in the coin scenario mentioned above, the user would only win 1 coin, because it is only executed, when the user "pauses" for the established time.
360 433
361 ## Simple State Manager 434 ## Simple State Manager
@@ -19,8 +19,14 @@ import '../navigation/routes/transitions_type.dart'; @@ -19,8 +19,14 @@ import '../navigation/routes/transitions_type.dart';
19 abstract class GetInterface { 19 abstract class GetInterface {
20 bool defaultPopGesture = GetPlatform.isIOS; 20 bool defaultPopGesture = GetPlatform.isIOS;
21 bool defaultOpaqueRoute = true; 21 bool defaultOpaqueRoute = true;
  22 +
22 Transition defaultTransition; 23 Transition defaultTransition;
23 - Duration defaultDurationTransition = Duration(milliseconds: 400); 24 + Duration defaultTransitionDuration = Duration(milliseconds: 400);
  25 + Curve defaultTransitionCurve = Curves.easeOutQuad;
  26 +
  27 + Curve defaultDialogTransitionCurve = Curves.easeOutQuad;
  28 + Duration defaultDialogTransitionDuration = Duration(milliseconds: 400);
  29 +
24 bool defaultGlobalState = true; 30 bool defaultGlobalState = true;
25 RouteSettings settings; 31 RouteSettings settings;
26 String defaultSeparator = "_"; 32 String defaultSeparator = "_";
@@ -43,6 +43,7 @@ extension GetNavigation on GetInterface { @@ -43,6 +43,7 @@ extension GetNavigation on GetInterface {
43 Widget page, { 43 Widget page, {
44 bool opaque, 44 bool opaque,
45 Transition transition, 45 Transition transition,
  46 + Curve curve,
46 Duration duration, 47 Duration duration,
47 int id, 48 int id,
48 bool fullscreenDialog = false, 49 bool fullscreenDialog = false,
@@ -66,9 +67,10 @@ extension GetNavigation on GetInterface { @@ -66,9 +67,10 @@ extension GetNavigation on GetInterface {
66 ), 67 ),
67 popGesture: popGesture ?? defaultPopGesture, 68 popGesture: popGesture ?? defaultPopGesture,
68 transition: transition ?? defaultTransition, 69 transition: transition ?? defaultTransition,
  70 + curve: curve ?? defaultTransitionCurve,
69 fullscreenDialog: fullscreenDialog, 71 fullscreenDialog: fullscreenDialog,
70 binding: binding, 72 binding: binding,
71 - transitionDuration: duration ?? defaultDurationTransition, 73 + transitionDuration: duration ?? defaultTransitionDuration,
72 ), 74 ),
73 ); 75 );
74 } 76 }
@@ -326,7 +328,8 @@ extension GetNavigation on GetInterface { @@ -326,7 +328,8 @@ extension GetNavigation on GetInterface {
326 /// It has the advantage of not needing context, 328 /// It has the advantage of not needing context,
327 /// so you can call from your business logic 329 /// so you can call from your business logic
328 /// 330 ///
329 - /// You can set a custom [transition], and a transition [duration]. 331 + /// You can set a custom [transition], define a Tween [curve],
  332 + /// and a transition [duration].
330 /// 333 ///
331 /// You can send any type of value to the other route in the [arguments]. 334 /// You can send any type of value to the other route in the [arguments].
332 /// 335 ///
@@ -347,6 +350,7 @@ extension GetNavigation on GetInterface { @@ -347,6 +350,7 @@ extension GetNavigation on GetInterface {
347 Widget page, { 350 Widget page, {
348 bool opaque = false, 351 bool opaque = false,
349 Transition transition, 352 Transition transition,
  353 + Curve curve,
350 bool popGesture, 354 bool popGesture,
351 int id, 355 int id,
352 Object arguments, 356 Object arguments,
@@ -368,7 +372,8 @@ extension GetNavigation on GetInterface { @@ -368,7 +372,8 @@ extension GetNavigation on GetInterface {
368 fullscreenDialog: fullscreenDialog, 372 fullscreenDialog: fullscreenDialog,
369 popGesture: popGesture ?? defaultPopGesture, 373 popGesture: popGesture ?? defaultPopGesture,
370 transition: transition ?? defaultTransition, 374 transition: transition ?? defaultTransition,
371 - transitionDuration: duration ?? defaultDurationTransition)); 375 + curve: curve ?? defaultTransitionCurve,
  376 + transitionDuration: duration ?? defaultTransitionDuration));
372 } 377 }
373 378
374 /// **Navigation.pushAndRemoveUntil()** shortcut .<br><br> 379 /// **Navigation.pushAndRemoveUntil()** shortcut .<br><br>
@@ -379,7 +384,7 @@ extension GetNavigation on GetInterface { @@ -379,7 +384,7 @@ extension GetNavigation on GetInterface {
379 /// It has the advantage of not needing context, 384 /// It has the advantage of not needing context,
380 /// so you can call from your business logic 385 /// so you can call from your business logic
381 /// 386 ///
382 - /// You can set a custom [transition], and a transition [duration]. 387 + /// You can set a custom [transition], a [curve] and a transition [duration].
383 /// 388 ///
384 /// You can send any type of value to the other route in the [arguments]. 389 /// You can send any type of value to the other route in the [arguments].
385 /// 390 ///
@@ -411,8 +416,9 @@ extension GetNavigation on GetInterface { @@ -411,8 +416,9 @@ extension GetNavigation on GetInterface {
411 Object arguments, 416 Object arguments,
412 Bindings binding, 417 Bindings binding,
413 bool fullscreenDialog = false, 418 bool fullscreenDialog = false,
414 - Duration duration,  
415 Transition transition, 419 Transition transition,
  420 + Curve curve,
  421 + Duration duration,
416 }) { 422 }) {
417 var routeName = "/${page.runtimeType.toString()}"; 423 var routeName = "/${page.runtimeType.toString()}";
418 424
@@ -424,15 +430,19 @@ extension GetNavigation on GetInterface { @@ -424,15 +430,19 @@ extension GetNavigation on GetInterface {
424 binding: binding, 430 binding: binding,
425 settings: RouteSettings(arguments: arguments), 431 settings: RouteSettings(arguments: arguments),
426 fullscreenDialog: fullscreenDialog, 432 fullscreenDialog: fullscreenDialog,
427 - routeName: routeName, 433 + routeName: routename,
428 transition: transition ?? defaultTransition, 434 transition: transition ?? defaultTransition,
429 - transitionDuration: duration ?? defaultDurationTransition, 435 + curve: curve ?? defaultTransitionCurve,
  436 + transitionDuration: duration ?? defaultTransitionDuration,
430 ), 437 ),
431 - predicate ?? (_) => false,  
432 - ); 438 + predicate ?? route);
  439 +
433 } 440 }
434 441
435 - /// Show a dialog 442 + /// Show a dialog.
  443 + /// You can pass a [transitionDuration] and/or [transitionCurve],
  444 + /// overriding the defaults when the dialog shows up and closes.
  445 + /// When the dialog closes, uses those animations in reverse.
436 Future<T> dialog<T>( 446 Future<T> dialog<T>(
437 Widget widget, { 447 Widget widget, {
438 bool barrierDismissible = true, 448 bool barrierDismissible = true,
@@ -440,6 +450,8 @@ extension GetNavigation on GetInterface { @@ -440,6 +450,8 @@ extension GetNavigation on GetInterface {
440 bool useSafeArea = true, 450 bool useSafeArea = true,
441 bool useRootNavigator = true, 451 bool useRootNavigator = true,
442 RouteSettings routeSettings, 452 RouteSettings routeSettings,
  453 + Duration transitionDuration,
  454 + Curve transitionCurve,
443 }) { 455 }) {
444 assert(widget != null); 456 assert(widget != null);
445 assert(barrierDismissible != null); 457 assert(barrierDismissible != null);
@@ -464,12 +476,12 @@ extension GetNavigation on GetInterface { @@ -464,12 +476,12 @@ extension GetNavigation on GetInterface {
464 barrierDismissible: barrierDismissible, 476 barrierDismissible: barrierDismissible,
465 barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, 477 barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
466 barrierColor: barrierColor ?? Colors.black54, 478 barrierColor: barrierColor ?? Colors.black54,
467 - transitionDuration: const Duration(milliseconds: 150), 479 + transitionDuration: transitionDuration ?? defaultDialogTransitionDuration,
468 transitionBuilder: (context, animation, secondaryAnimation, child) { 480 transitionBuilder: (context, animation, secondaryAnimation, child) {
469 return FadeTransition( 481 return FadeTransition(
470 opacity: CurvedAnimation( 482 opacity: CurvedAnimation(
471 parent: animation, 483 parent: animation,
472 - curve: Curves.easeOut, 484 + curve: transitionCurve ?? defaultDialogTransitionCurve,
473 ), 485 ),
474 child: child, 486 child: child,
475 ); 487 );
@@ -874,7 +886,7 @@ extension GetNavigation on GetInterface { @@ -874,7 +886,7 @@ extension GetNavigation on GetInterface {
874 } 886 }
875 887
876 if (defaultDurationTransition != null) { 888 if (defaultDurationTransition != null) {
877 - this.defaultDurationTransition = defaultDurationTransition; 889 + this.defaultTransitionDuration = defaultDurationTransition;
878 } 890 }
879 891
880 if (defaultGlobalState != null) { 892 if (defaultGlobalState != null) {
1 import 'package:flutter/foundation.dart'; 1 import 'package:flutter/foundation.dart';
2 import 'package:flutter/material.dart'; 2 import 'package:flutter/material.dart';
3 -  
4 import '../../../get.dart'; 3 import '../../../get.dart';
5 import '../../core/log.dart'; 4 import '../../core/log.dart';
6 import '../../instance/get_instance.dart'; 5 import '../../instance/get_instance.dart';
@@ -211,7 +210,7 @@ class GetMaterialApp extends StatelessWidget { @@ -211,7 +210,7 @@ class GetMaterialApp extends StatelessWidget {
211 defaultOpaqueRoute: opaqueRoute ?? Get.isOpaqueRouteDefault, 210 defaultOpaqueRoute: opaqueRoute ?? Get.isOpaqueRouteDefault,
212 defaultPopGesture: popGesture ?? Get.isPopGestureEnable, 211 defaultPopGesture: popGesture ?? Get.isPopGestureEnable,
213 defaultDurationTransition: 212 defaultDurationTransition:
214 - transitionDuration ?? Get.defaultDurationTransition, 213 + transitionDuration ?? Get.defaultTransitionDuration,
215 defaultGlobalState: defaultGlobalState ?? Get.defaultGlobalState, 214 defaultGlobalState: defaultGlobalState ?? Get.defaultGlobalState,
216 ); 215 );
217 }, 216 },
  1 +import 'dart:ui';
1 import '../../../get.dart'; 2 import '../../../get.dart';
2 3
3 /// [Bindings] should be extended or implemented. 4 /// [Bindings] should be extended or implemented.
@@ -18,14 +19,20 @@ abstract class Bindings { @@ -18,14 +19,20 @@ abstract class Bindings {
18 /// GetPage( 19 /// GetPage(
19 /// name: '/', 20 /// name: '/',
20 /// page: () => Home(), 21 /// page: () => Home(),
21 -/// binding: BindingsBuilder(() => Get.put(HomeController())), 22 +/// // This might cause you an error.
  23 +/// // binding: BindingsBuilder(() => Get.put(HomeController())),
  24 +/// binding: BindingsBuilder(() { Get.put(HomeController(); })),
  25 +/// // Using .lazyPut() works fine.
  26 +/// // binding: BindingsBuilder(() => Get.lazyPut(() => HomeController())),
22 /// ), 27 /// ),
23 /// ``` 28 /// ```
24 class BindingsBuilder<T> extends Bindings { 29 class BindingsBuilder<T> extends Bindings {
25 /// Register your dependencies in the [builder] callback. 30 /// Register your dependencies in the [builder] callback.
26 - final void Function() builder; 31 + final VoidCallback builder;
27 32
28 - /// Shortcut to register 1 Controller with Get.put(). 33 + /// Shortcut to register 1 Controller with Get.put(),
  34 + /// Prevents the issue of the fat arrow function with the constructor.
  35 + /// BindingsBuilder(() => Get.put(HomeController())),
29 /// 36 ///
30 /// Sample: 37 /// Sample:
31 /// ``` 38 /// ```
@@ -41,6 +48,9 @@ class BindingsBuilder<T> extends Bindings { @@ -41,6 +48,9 @@ class BindingsBuilder<T> extends Bindings {
41 .put<T>(null, tag: tag, permanent: permanent, builder: builder)); 48 .put<T>(null, tag: tag, permanent: permanent, builder: builder));
42 } 49 }
43 50
  51 + /// WARNING: don't use `()=> Get.put(Controller())`,
  52 + /// if only passing 1 callback use `BindingsBuilder.put(Controller())`
  53 + /// or `BindingsBuilder(()=> Get.lazyPut(Controller()))`
44 BindingsBuilder(this.builder); 54 BindingsBuilder(this.builder);
45 55
46 @override 56 @override
@@ -4,7 +4,6 @@ import 'dart:ui' show lerpDouble; @@ -4,7 +4,6 @@ import 'dart:ui' show lerpDouble;
4 import 'package:flutter/cupertino.dart'; 4 import 'package:flutter/cupertino.dart';
5 import 'package:flutter/gestures.dart'; 5 import 'package:flutter/gestures.dart';
6 import 'package:flutter/material.dart'; 6 import 'package:flutter/material.dart';
7 -  
8 import '../../../route_manager.dart'; 7 import '../../../route_manager.dart';
9 import '../../../utils.dart'; 8 import '../../../utils.dart';
10 import '../../core/get_main.dart'; 9 import '../../core/get_main.dart';
@@ -143,18 +142,23 @@ class GetPageRoute<T> extends PageRoute<T> { @@ -143,18 +142,23 @@ class GetPageRoute<T> extends PageRoute<T> {
143 @override 142 @override
144 Widget buildTransitions(BuildContext context, Animation<double> animation, 143 Widget buildTransitions(BuildContext context, Animation<double> animation,
145 Animation<double> secondaryAnimation, Widget child) { 144 Animation<double> secondaryAnimation, Widget child) {
  145 + final finalCurve = curve ?? Get.defaultTransitionCurve;
  146 + final hasCurve = curve != null;
146 if (fullscreenDialog && transition == null) { 147 if (fullscreenDialog && transition == null) {
  148 + /// by default, if no curve is defined, use Cupertino transition in the
  149 + /// default way (no linearTransition)... otherwise take the curve passed.
147 return CupertinoFullscreenDialogTransition( 150 return CupertinoFullscreenDialogTransition(
148 - primaryRouteAnimation: animation, 151 + primaryRouteAnimation: hasCurve
  152 + ? CurvedAnimation(parent: animation, curve: finalCurve)
  153 + : animation,
149 secondaryRouteAnimation: secondaryAnimation, 154 secondaryRouteAnimation: secondaryAnimation,
150 child: child, 155 child: child,
151 - linearTransition: true); 156 + linearTransition: hasCurve);
152 } 157 }
153 -  
154 - if (customTransition != null) {  
155 - return customTransition.buildTransition( 158 + if (this.customTransition != null) {
  159 + return this.customTransition.buildTransition(
156 context, 160 context,
157 - curve, 161 + finalCurve,
158 alignment, 162 alignment,
159 animation, 163 animation,
160 secondaryAnimation, 164 secondaryAnimation,
@@ -165,8 +169,13 @@ class GetPageRoute<T> extends PageRoute<T> { @@ -165,8 +169,13 @@ class GetPageRoute<T> extends PageRoute<T> {
165 child: child) 169 child: child)
166 : child, 170 : child,
167 ); 171 );
  172 +
168 } 173 }
169 174
  175 + /// Apply the curve by default...
  176 + final iosAnimation = animation;
  177 + animation = CurvedAnimation(parent: animation, curve: finalCurve);
  178 +
170 switch (transition ?? Get.defaultTransition) { 179 switch (transition ?? Get.defaultTransition) {
171 case Transition.leftToRight: 180 case Transition.leftToRight:
172 return SlideLeftTransition().buildTransitions( 181 return SlideLeftTransition().buildTransitions(
@@ -291,9 +300,9 @@ class GetPageRoute<T> extends PageRoute<T> { @@ -291,9 +300,9 @@ class GetPageRoute<T> extends PageRoute<T> {
291 case Transition.cupertino: 300 case Transition.cupertino:
292 return CupertinoTransitions().buildTransitions( 301 return CupertinoTransitions().buildTransitions(
293 context, 302 context,
294 - curve, 303 + hasCurve,
295 alignment, 304 alignment,
296 - animation, 305 + hasCurve ? animation : iosAnimation,
297 secondaryAnimation, 306 secondaryAnimation,
298 popGesture ?? Get.defaultPopGesture 307 popGesture ?? Get.defaultPopGesture
299 ? _CupertinoBackGestureDetector<T>( 308 ? _CupertinoBackGestureDetector<T>(
@@ -343,40 +352,13 @@ class GetPageRoute<T> extends PageRoute<T> { @@ -343,40 +352,13 @@ class GetPageRoute<T> extends PageRoute<T> {
343 : child); 352 : child);
344 353
345 case Transition.native: 354 case Transition.native:
346 - if (GetPlatform.isIOS) {  
347 - return CupertinoTransitions().buildTransitions(  
348 - context,  
349 - curve,  
350 - alignment,  
351 - animation,  
352 - secondaryAnimation,  
353 - popGesture ?? Get.defaultPopGesture  
354 - ? _CupertinoBackGestureDetector<T>(  
355 - enabledCallback: () => _isPopGestureEnabled<T>(this),  
356 - onStartPopGesture: () => _startPopGesture<T>(this),  
357 - child: child)  
358 - : child);  
359 - }  
360 -  
361 - return FadeUpwardsPageTransitionsBuilder().buildTransitions(  
362 - this,  
363 - context,  
364 - animation,  
365 - secondaryAnimation,  
366 - popGesture ?? Get.defaultPopGesture  
367 - ? _CupertinoBackGestureDetector<T>(  
368 - enabledCallback: () => _isPopGestureEnabled<T>(this),  
369 - onStartPopGesture: () => _startPopGesture<T>(this),  
370 - child: child)  
371 - : child);  
372 -  
373 default: 355 default:
374 if (GetPlatform.isIOS) { 356 if (GetPlatform.isIOS) {
375 return CupertinoTransitions().buildTransitions( 357 return CupertinoTransitions().buildTransitions(
376 context, 358 context,
377 - curve, 359 + hasCurve,
378 alignment, 360 alignment,
379 - animation, 361 + hasCurve ? animation : iosAnimation,
380 secondaryAnimation, 362 secondaryAnimation,
381 popGesture ?? Get.defaultPopGesture 363 popGesture ?? Get.defaultPopGesture
382 ? _CupertinoBackGestureDetector<T>( 364 ? _CupertinoBackGestureDetector<T>(
@@ -188,7 +188,7 @@ class SizeTransitions { @@ -188,7 +188,7 @@ class SizeTransitions {
188 class CupertinoTransitions { 188 class CupertinoTransitions {
189 Widget buildTransitions( 189 Widget buildTransitions(
190 BuildContext context, 190 BuildContext context,
191 - Curve curve, 191 + bool useLinearTransition,
192 Alignment alignment, 192 Alignment alignment,
193 Animation<double> animation, 193 Animation<double> animation,
194 Animation<double> secondaryAnimation, 194 Animation<double> secondaryAnimation,
@@ -196,7 +196,7 @@ class CupertinoTransitions { @@ -196,7 +196,7 @@ class CupertinoTransitions {
196 return CupertinoPageTransition( 196 return CupertinoPageTransition(
197 primaryRouteAnimation: animation, 197 primaryRouteAnimation: animation,
198 secondaryRouteAnimation: secondaryAnimation, 198 secondaryRouteAnimation: secondaryAnimation,
199 - linearTransition: true, 199 + linearTransition: useLinearTransition,
200 child: child, 200 child: child,
201 ); 201 );
202 } 202 }
@@ -23,7 +23,7 @@ class _RxImpl<T> implements RxInterface<T> { @@ -23,7 +23,7 @@ class _RxImpl<T> implements RxInterface<T> {
23 /// 23 ///
24 /// WARNING: still WIP, needs testing! 24 /// WARNING: still WIP, needs testing!
25 _RxImpl<T> operator <<(T val) { 25 _RxImpl<T> operator <<(T val) {
26 - subject.add(value = val); 26 + subject.add(_value = val);
27 return this; 27 return this;
28 } 28 }
29 29
@@ -94,9 +94,9 @@ class _RxImpl<T> implements RxInterface<T> { @@ -94,9 +94,9 @@ class _RxImpl<T> implements RxInterface<T> {
94 /// }); 94 /// });
95 /// print( person ); 95 /// print( person );
96 /// ``` 96 /// ```
97 - void update(void fn(T value)) {  
98 - fn(value);  
99 - subject.add(value); 97 + void update(void fn(T val)) {
  98 + fn(_value);
  99 + subject.add(_value);
100 } 100 }
101 101
102 String get string => value.toString(); 102 String get string => value.toString();
@@ -179,60 +179,52 @@ class RxBool extends _RxImpl<bool> { @@ -179,60 +179,52 @@ class RxBool extends _RxImpl<bool> {
179 } 179 }
180 } 180 }
181 181
182 -class RxDouble extends _RxImpl<double> {  
183 - RxDouble([double initial]) {  
184 - _value = initial;  
185 - }  
186 -  
187 - RxDouble operator +(double val) {  
188 - subject.add(value += val); 182 +abstract class _BaseRxNum<T> extends _RxImpl<num> {
  183 + _BaseRxNum operator +(num val) {
  184 + subject.add(_value += val);
189 return this; 185 return this;
190 } 186 }
191 187
192 - RxDouble operator -(double val) {  
193 - subject.add(value -= val); 188 + _BaseRxNum operator -(num val) {
  189 + subject.add(_value -= val);
194 return this; 190 return this;
195 } 191 }
196 192
197 - RxDouble operator /(double val) {  
198 - subject.add(value /= val); 193 + _BaseRxNum operator /(num val) {
  194 + subject.add(_value /= val);
199 return this; 195 return this;
200 } 196 }
201 197
202 - RxDouble operator *(double val) {  
203 - subject.add(value *= val); 198 + _BaseRxNum operator *(num val) {
  199 + subject.add(_value *= val);
204 return this; 200 return this;
205 } 201 }
206 -}  
207 -  
208 -class RxNum extends _RxImpl<num> {  
209 - RxNum([num initial]) {  
210 - _value = initial;  
211 - }  
212 202
213 - RxNum operator >>(num val) {  
214 - subject.add(value = val); 203 + _BaseRxNum operator ~/(num val) {
  204 + subject.add(_value ~/ val);
215 return this; 205 return this;
216 } 206 }
217 207
218 - RxNum operator +(num val) {  
219 - subject.add(value += val); 208 + _BaseRxNum operator %(num val) {
  209 + subject.add(_value % val);
220 return this; 210 return this;
221 } 211 }
222 212
223 - RxNum operator -(num val) {  
224 - subject.add(value -= val);  
225 - return this;  
226 - } 213 + bool operator <=(num other) => _value <= other;
  214 + bool operator >=(num other) => _value >= other;
  215 + bool operator <(num other) => _value < other;
  216 + bool operator >(num other) => _value > other;
  217 +}
227 218
228 - RxNum operator /(num val) {  
229 - subject.add(value /= val);  
230 - return this; 219 +class RxDouble extends _BaseRxNum<double> {
  220 + RxDouble([double initial]) {
  221 + _value = initial;
231 } 222 }
  223 +}
232 224
233 - RxNum operator *(num val) {  
234 - subject.add(value *= val);  
235 - return this; 225 +class RxNum extends _BaseRxNum<num> {
  226 + RxNum([num initial]) {
  227 + _value = initial;
236 } 228 }
237 } 229 }
238 230
@@ -241,51 +233,21 @@ class RxString extends _RxImpl<String> { @@ -241,51 +233,21 @@ class RxString extends _RxImpl<String> {
241 _value = initial; 233 _value = initial;
242 } 234 }
243 235
244 - RxString operator >>(String val) {  
245 - subject.add(value = val);  
246 - return this;  
247 - }  
248 -  
249 RxString operator +(String val) { 236 RxString operator +(String val) {
250 - subject.add(value += val); 237 + subject.add(_value += val);
251 return this; 238 return this;
252 } 239 }
253 240
254 RxString operator *(int val) { 241 RxString operator *(int val) {
255 - subject.add(value *= val); 242 + subject.add(_value *= val);
256 return this; 243 return this;
257 } 244 }
258 } 245 }
259 246
260 -class RxInt extends _RxImpl<int> { 247 +class RxInt extends _BaseRxNum<int> {
261 RxInt([int initial]) { 248 RxInt([int initial]) {
262 _value = initial; 249 _value = initial;
263 } 250 }
264 -  
265 - RxInt operator >>(int val) {  
266 - subject.add(value = val);  
267 - return this;  
268 - }  
269 -  
270 - RxInt operator +(int val) {  
271 - subject.add(value += val);  
272 - return this;  
273 - }  
274 -  
275 - RxInt operator -(int val) {  
276 - subject.add(value -= val);  
277 - return this;  
278 - }  
279 -  
280 - RxInt operator /(int val) {  
281 - subject.add(value ~/= val);  
282 - return this;  
283 - }  
284 -  
285 - RxInt operator *(int val) {  
286 - subject.add(value *= val);  
287 - return this;  
288 - }  
289 } 251 }
290 252
291 class Rx<T> extends _RxImpl<T> { 253 class Rx<T> extends _RxImpl<T> {
1 import 'dart:async'; 1 import 'dart:async';
2 -  
3 import '../../../../get.dart'; 2 import '../../../../get.dart';
4 import '../rx_core/rx_interface.dart'; 3 import '../rx_core/rx_interface.dart';
5 import 'utils/debouncer.dart'; 4 import 'utils/debouncer.dart';
6 5
  6 +bool _conditional(dynamic condition) {
  7 + if (condition == null) return true;
  8 + if (condition is bool) return condition;
  9 + if (condition is bool Function()) return condition();
  10 + return true;
  11 +}
  12 +
  13 +///
  14 +/// Called every time [listener] changes. As long as the [condition] returns true.
  15 +///
  16 +/// Sample:
  17 +/// Every time increment() is called, ever() will process the [condition]
  18 +/// (can be a [bool] expression or a [bool Function()]), and only call the callback
  19 +/// when [condition] is true.
  20 +/// In our case, only when count is bigger to 5. In order to "dispose" this Worker
  21 +/// that will run forever, we made a [worker] variable. So, when the count value
  22 +/// reaches 10, the worker gets disposed, and releases any memory resources.
  23 +///
  24 +/// ```
  25 +/// // imagine some counter widget...
  26 +///
  27 +/// class _CountController extends GetxController {
  28 +/// final count = 0.obs;
  29 +/// Worker worker;
  30 +///
  31 +/// void onInit() {
  32 +/// worker = ever(count, (value) {
  33 +/// print('counter changed to: $value');
  34 +/// if (value == 10) worker.dispose();
  35 +/// }, condition: () => count > 5);
  36 +/// }
  37 +///
  38 +/// void increment() => count + 1;
  39 +/// }
  40 +/// ```
7 Worker ever<T>(RxInterface<T> listener, Function(T) callback, 41 Worker ever<T>(RxInterface<T> listener, Function(T) callback,
8 - {bool condition = true}) { 42 + {dynamic condition = true}) {
9 StreamSubscription sub = listener.subject.stream.listen((event) { 43 StreamSubscription sub = listener.subject.stream.listen((event) {
10 - if (condition) callback(event); 44 + if (_conditional(condition)) callback(event);
11 }); 45 });
12 -  
13 - Future<void> cancel() {  
14 - return sub.cancel();  
15 - }  
16 -  
17 - return Worker(cancel, '[ever]'); 46 + return Worker(sub.cancel, '[ever]');
18 } 47 }
19 48
  49 +/// Similar to [ever], but takes a list of [listeners], the condition for the [callback]
  50 +/// is common to all [listeners], and the [callback] is executed to each one of them.
  51 +/// The [Worker] is common to all, so [worker.dispose()] will cancel all streams.
20 Worker everAll(List<RxInterface> listeners, Function(dynamic) callback, 52 Worker everAll(List<RxInterface> listeners, Function(dynamic) callback,
21 - {bool condition = true}) {  
22 - var evers = <StreamSubscription>[];  
23 -  
24 - for (final listener in listeners) {  
25 - final sub = listener.subject.stream.listen((event) {  
26 - if (condition) {  
27 - callback(event);  
28 - } 53 + {dynamic condition = true}) {
  54 + List<StreamSubscription> evers = <StreamSubscription>[];
  55 + for (var i in listeners) {
  56 + StreamSubscription sub = i.subject.stream.listen((event) {
  57 + if (_conditional(condition)) callback(event);
29 }); 58 });
30 evers.add(sub); 59 evers.add(sub);
31 } 60 }
32 61
33 Future<void> cancel() { 62 Future<void> cancel() {
34 - for (var i in evers) {  
35 - i.cancel();  
36 - } 63 + for (var i in evers) i.cancel();
37 return Future.value(() {}); 64 return Future.value(() {});
38 } 65 }
39 66
40 return Worker(cancel, '[everAll]'); 67 return Worker(cancel, '[everAll]');
41 } 68 }
42 69
43 -Worker once<T>(  
44 - RxInterface<T> listener,  
45 - Function(T) callback, {  
46 - bool condition = true,  
47 -}) {  
48 - StreamSubscription subscription;  
49 - var times = 0;  
50 70
51 - subscription = listener.subject.stream.listen((event) {  
52 - if (!condition) return null;  
53 - times++;  
54 - if (times < 2) { 71 +/// [once()] will execute only 1 time when [condition] is met and cancel
  72 +/// the subscription to the [listener] stream right after that.
  73 +/// [condition] defines when [callback] is called, and
  74 +/// can be a [bool] or a [bool Function()].
  75 +///
  76 +/// Sample:
  77 +/// ```
  78 +/// class _CountController extends GetxController {
  79 +/// final count = 0.obs;
  80 +/// Worker worker;
  81 +///
  82 +/// @override
  83 +/// Future<void> onInit() async {
  84 +/// worker = once(count, (value) {
  85 +/// print("counter reached $value before 3 seconds.");
  86 +/// }, condition: () => count() > 2);
  87 +/// 3.delay(worker.dispose);
  88 +/// }
  89 +/// void increment() => count + 1;
  90 +/// }
  91 +///```
  92 +Worker once<T>(RxInterface<T> listener, Function(T) callback,
  93 + {dynamic condition}) {
  94 + Worker ref;
  95 + StreamSubscription sub;
  96 + sub = listener.subject.stream.listen((event) {
  97 + if (!_conditional(condition)) return;
  98 + ref._disposed = true;
  99 + ref._log('called');
  100 + sub?.cancel();
55 callback(event); 101 callback(event);
56 - } else {  
57 - subscription.cancel();  
58 - }  
59 }); 102 });
60 -  
61 - Future<void> cancel() {  
62 - return subscription.cancel();  
63 - }  
64 -  
65 - return Worker(cancel, '[once]'); 103 + ref = Worker(sub.cancel, '[once]');
  104 + return ref;
66 } 105 }
67 106
  107 +/// Ignore all changes in [listener] during [time] (1 sec by default) or until
  108 +/// [condition] is met (can be a [bool] expression or a [bool Function()]),
  109 +/// It brings the 1st "value" since the period of time, so
  110 +/// if you click a counter button 3 times in 1 sec, it will show you "1" (after 1 sec of the first press)
  111 +/// click counter 3 times in 1 sec, it will show you "4" (after 1 sec)
  112 +/// click counter 2 times in 1 sec, it will show you "7" (after 1 sec).
  113 +///
  114 +/// Sample:
  115 +/// // wait 1 sec each time an event starts, only if counter is lower than 20.
  116 +/// worker = interval(
  117 +/// count,
  118 +/// (value) => print(value),
  119 +/// time: 1.seconds,
  120 +/// condition: () => count < 20,
  121 +/// );
  122 +/// ```
68 Worker interval<T>(RxInterface<T> listener, Function(T) callback, 123 Worker interval<T>(RxInterface<T> listener, Function(T) callback,
69 - {Duration time, bool condition = true}) {  
70 - var debounceActive = false; 124 + {Duration time = const Duration(seconds: 1), dynamic condition = true}) {
  125 + bool debounceActive = false;
  126 + time ??= const Duration(seconds: 1);
71 StreamSubscription sub = listener.subject.stream.listen((event) async { 127 StreamSubscription sub = listener.subject.stream.listen((event) async {
72 - if (debounceActive || !condition) return null; 128 + if (debounceActive || !_conditional(condition)) return;
73 debounceActive = true; 129 debounceActive = true;
74 - await Future.delayed(time ?? Duration(seconds: 1)); 130 + await Future.delayed(time);
75 debounceActive = false; 131 debounceActive = false;
76 callback(event); 132 callback(event);
77 }); 133 });
78 -  
79 - Future<void> cancel() {  
80 - return sub.cancel();  
81 - }  
82 -  
83 - return Worker(cancel, '[interval]'); 134 + return Worker(sub?.cancel, '[interval]');
84 } 135 }
85 136
  137 +/// [debounce] is similar to [interval], but sends the last value.
  138 +/// Useful for Anti DDos, every time the user stops typing for 1 second, for instance.
  139 +/// When [listener] emits the last "value", when [time] hits, it calls [callback]
  140 +/// with the last "value" emitted.
  141 +///
  142 +/// Sample:
  143 +///
  144 +/// ```
  145 +/// worker = debounce(
  146 +/// count,
  147 +/// (value) {
  148 +/// print(value);
  149 +/// if( value > 20 ) worker.dispose();
  150 +/// },
  151 +/// time: 1.seconds,
  152 +/// );
  153 +/// }
  154 +/// ```
86 Worker debounce<T>(RxInterface<T> listener, Function(T) callback, 155 Worker debounce<T>(RxInterface<T> listener, Function(T) callback,
87 {Duration time}) { 156 {Duration time}) {
88 - final _debouncer = Debouncer(delay: time ?? Duration(milliseconds: 800)); 157 + final _debouncer =
  158 + Debouncer(delay: time ?? const Duration(milliseconds: 800));
89 StreamSubscription sub = listener.subject.stream.listen((event) { 159 StreamSubscription sub = listener.subject.stream.listen((event) {
90 _debouncer(() { 160 _debouncer(() {
91 callback(event); 161 callback(event);
92 }); 162 });
93 }); 163 });
94 -  
95 - Future<void> cancel() {  
96 - return sub.cancel();  
97 - }  
98 -  
99 - return Worker(cancel, '[debounce]'); 164 + return Worker(sub.cancel, '[debounce]');
100 } 165 }
101 166
102 class Worker { 167 class Worker {
103 Worker(this.worker, this.type); 168 Worker(this.worker, this.type);
104 169
  170 + /// subscription.cancel() callback
105 final Future<void> Function() worker; 171 final Future<void> Function() worker;
  172 +
  173 + /// type of worker (debounce, interval, ever)..
106 final String type; 174 final String type;
  175 + bool _disposed = false;
107 176
108 - void _message() {  
109 - GetConfig.log('Worker $type disposed'); 177 + bool _verbose = true;
  178 + void _log(String msg) {
  179 + if (!_verbose) return;
  180 + GetConfig.log('$runtimeType $type $msg');
110 } 181 }
111 182
112 void dispose() { 183 void dispose() {
113 - worker();  
114 - _message(); 184 + if (_disposed) {
  185 + _log('already disposed');
  186 + return;
115 } 187 }
116 -  
117 - void call() { 188 + _disposed = true;
118 worker(); 189 worker();
119 - _message(); 190 + _log('disposed');
120 } 191 }
  192 +
  193 + void call() => dispose();
121 } 194 }
@@ -8,46 +8,122 @@ import '../../navigation/root/smart_management.dart'; @@ -8,46 +8,122 @@ import '../../navigation/root/smart_management.dart';
8 import '../rx/rx_core/rx_interface.dart'; 8 import '../rx/rx_core/rx_interface.dart';
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 +// if it brings overhead the extra call,
  16 +typedef GetStateUpdate = bool Function();
  17 +//typedef GetStateUpdate = void Function(VoidCallback fn);
  18 +
  19 +/// Complies with [GetStateUpdater]
  20 +///
  21 +/// This mixin's function represents a [GetStateUpdater], and might be used
  22 +/// by [GetBuilder()], [SimpleBuilder()] (or similar) to comply
  23 +/// with [GetStateUpdate] signature. REPLACING the [StateSetter].
  24 +/// Avoids the potential (but extremely unlikely) issue of having
  25 +/// the Widget in a dispose() state, and abstracts the API from the ugly fn((){}).
  26 +/// TODO: check performance HIT for the extra method call.
  27 +///
  28 +mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> {
  29 + // To avoid the creation of an anonym function to be GC later.
  30 + static VoidCallback _stateCallback = () {};
  31 +
  32 + /// Experimental method to replace setState((){});
  33 + /// Used with GetStateUpdate.
  34 + bool getUpdate() {
  35 + final _mounted = mounted;
  36 + if (_mounted) setState(_stateCallback);
  37 + return _mounted;
  38 + }
  39 +}
12 40
13 class GetxController extends DisposableInterface { 41 class GetxController extends DisposableInterface {
14 - final HashSet<StateSetter> _updaters = HashSet<StateSetter>(); 42 + final _updaters = HashSet<GetStateUpdate>();
  43 +
  44 +// final _updatersIds = HashMap<String, StateSetter>(); //<old>
  45 + final _updatersIds = HashMap<String, GetStateUpdate>();
15 46
16 - final HashMap<String, StateSetter> _updatersIds =  
17 - HashMap<String, StateSetter>(); 47 + final _updatersGroupIds = HashMap<String, HashSet<GetStateUpdate>>();
18 48
19 - /// Update GetBuilder with update(); 49 + /// Rebuilds [GetBuilder] each time you call [update()];
  50 + /// Can take a List of [ids], that will only update the matching
  51 + /// `GetBuilder( id: )`,
  52 + /// [ids] can be reused among `GetBuilders` like group tags.
  53 + /// The update will only notify the Widgets, if [condition] is true.
20 void update([List<String> ids, bool condition = true]) { 54 void update([List<String> ids, bool condition = true]) {
  55 +
21 if (!condition) { 56 if (!condition) {
22 return; 57 return;
23 } 58 }
24 -  
25 if (ids == null) { 59 if (ids == null) {
  60 +// _updaters?.forEach((rs) => rs(() {})); //<old>
26 for (final updater in _updaters) { 61 for (final updater in _updaters) {
27 - updater(() {}); 62 + updater();
28 } 63 }
29 } else { 64 } else {
30 - for (final id in ids) {  
31 - _updatersIds[id]?.call(() {});  
32 - } 65 + // @jonny, remove this commented code if it's not more optimized.
  66 +// for (final id in ids) {
  67 +// if (_updatersIds[id] != null) _updatersIds[id]();
  68 +// if (_updatersGroupIds[id] != null)
  69 +// for (final rs in _updatersGroupIds[id]) rs();
  70 +// }
  71 +
  72 + ids.forEach((id) {
  73 +// _updatersIds[id]?.call(() {}); //<old>
  74 +// _updatersGroupIds[id]?.forEach((rs) => rs(() {})); //<old>
  75 + _updatersIds[id]?.call();
  76 + _updatersGroupIds[id]?.forEach((rs) => rs());
  77 + });
33 } 78 }
34 } 79 }
35 80
36 - Disposer addListener(StateSetter listener) { 81 +// VoidCallback addListener(StateSetter listener) {//<old>
  82 + VoidCallback addListener(GetStateUpdate listener) {
37 _updaters.add(listener); 83 _updaters.add(listener);
38 return () => _updaters.remove(listener); 84 return () => _updaters.remove(listener);
39 } 85 }
40 86
41 - // void removeListener(StateSetter listener) {  
42 - // _updaters.remove(listener);  
43 - // }  
44 -  
45 - Disposer addListenerId(String key, StateSetter listener) { 87 +// VoidCallback addListenerId(String key, StateSetter listener) {//<old>
  88 + VoidCallback addListenerId(String key, GetStateUpdate listener) {
  89 +// _printCurrentIds();
  90 + if (_updatersIds.containsKey(key)) {
  91 + _updatersGroupIds[key] ??= HashSet<GetStateUpdate>.identity();
  92 + _updatersGroupIds[key].add(listener);
  93 + return () {
  94 + _updatersGroupIds[key].remove(listener);
  95 + };
  96 + } else {
46 _updatersIds[key] = listener; 97 _updatersIds[key] = listener;
47 return () => _updatersIds.remove(key); 98 return () => _updatersIds.remove(key);
48 } 99 }
  100 + }
49 101
50 - void disposeKey(String key) => _updatersIds.remove(key); 102 + /// To dispose an [id] from future updates(), this ids are registered
  103 + /// by [GetBuilder()] or similar, so is a way to unlink the state change with
  104 + /// the Widget from the Controller.
  105 + void disposeId(String id) {
  106 + _updatersIds.remove(id);
  107 + _updatersGroupIds.remove(id);
  108 + }
  109 +
  110 + /// Remove this after checking the new implementation makes sense.
  111 + /// Uncomment this if you wanna control the removal of ids..
  112 +// bool _debugging = false;
  113 +// Future<void> _printCurrentIds() async {
  114 +// if (_debugging) return;
  115 +// _debugging = true;
  116 +// print('about to debug...');
  117 +// await Future.delayed(Duration(milliseconds: 10));
  118 +// int totalGroups = 0;
  119 +// _updatersGroupIds.forEach((key, value) {
  120 +// totalGroups += value.length;
  121 +// });
  122 +// int totalIds = _updatersIds.length;
  123 +// print(
  124 +// 'Total: ${totalIds + totalGroups}, in groups:$totalGroups, solo ids:$totalIds');
  125 +// _debugging = false;
  126 +// }
51 } 127 }
52 128
53 class GetBuilder<T extends GetxController> extends StatefulWidget { 129 class GetBuilder<T extends GetxController> extends StatefulWidget {
@@ -81,11 +157,13 @@ class GetBuilder<T extends GetxController> extends StatefulWidget { @@ -81,11 +157,13 @@ class GetBuilder<T extends GetxController> extends StatefulWidget {
81 _GetBuilderState<T> createState() => _GetBuilderState<T>(); 157 _GetBuilderState<T> createState() => _GetBuilderState<T>();
82 } 158 }
83 159
84 -class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> {  
85 - T controller; 160 +
  161 +class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
  162 + with GetStateUpdaterMixin {
  163 + GetxController controller;
  164 +
86 bool isCreator = false; 165 bool isCreator = false;
87 - final HashSet<Disposer> disposers = HashSet<Disposer>();  
88 - Disposer remove; 166 + VoidCallback remove;
89 167
90 @override 168 @override
91 void initState() { 169 void initState() {
@@ -119,28 +197,40 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> { @@ -119,28 +197,40 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> {
119 GetConfig.smartManagement == SmartManagement.onlyBuilder) { 197 GetConfig.smartManagement == SmartManagement.onlyBuilder) {
120 controller?.onStart(); 198 controller?.onStart();
121 } 199 }
  200 + _subscribeToController();
  201 + }
  202 +
  203 + /// Register to listen Controller's events.
  204 + /// It gets a reference to the remove() callback, to delete the
  205 + /// setState "link" from the Controller.
  206 + void _subscribeToController() {
  207 + remove?.call();
122 remove = (widget.id == null) 208 remove = (widget.id == null)
123 - ? controller?.addListener(setState)  
124 - : controller?.addListenerId(widget.id, setState); 209 +// ? controller?.addListener(setState) //<old>
  210 +// : controller?.addListenerId(widget.id, setState); //<old>
  211 + ? controller?.addListener(getUpdate)
  212 + : controller?.addListenerId(widget.id, getUpdate);
125 } 213 }
126 214
  215 + /// Sample for [GetStateUpdate] when you don't wanna use [GetStateHelper mixin].
  216 +// bool _getUpdater() {
  217 +// final _mounted = mounted;
  218 +// if (_mounted) setState(() {});
  219 +// return _mounted;
  220 +// }
  221 +
127 @override 222 @override
128 void dispose() { 223 void dispose() {
129 super.dispose(); 224 super.dispose();
130 if (widget.dispose != null) widget.dispose(this); 225 if (widget.dispose != null) widget.dispose(this);
131 if (isCreator || widget.assignId) { 226 if (isCreator || widget.assignId) {
132 if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) { 227 if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
133 - if (remove != null) remove();  
134 -  
135 GetInstance().delete<T>(tag: widget.tag); 228 GetInstance().delete<T>(tag: widget.tag);
136 } 229 }
137 - } else {  
138 - if (remove != null) remove();  
139 } 230 }
140 231
141 - for (final disposer in disposers) {  
142 - disposer();  
143 - } 232 + remove?.call();
  233 +
144 } 234 }
145 235
146 @override 236 @override
@@ -154,6 +244,10 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> { @@ -154,6 +244,10 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> {
154 @override 244 @override
155 void didUpdateWidget(GetBuilder oldWidget) { 245 void didUpdateWidget(GetBuilder oldWidget) {
156 super.didUpdateWidget(oldWidget as GetBuilder<T>); 246 super.didUpdateWidget(oldWidget as GetBuilder<T>);
  247 + // to avoid conflicts when modifying a "grouped" id list.
  248 + if (oldWidget.id != widget.id) {
  249 + _subscribeToController();
  250 + }
157 if (widget.didUpdateWidget != null) widget.didUpdateWidget(oldWidget, this); 251 if (widget.didUpdateWidget != null) widget.didUpdateWidget(oldWidget, this);
158 } 252 }
159 253
@@ -88,8 +88,9 @@ class SimpleBuilder extends StatefulWidget { @@ -88,8 +88,9 @@ class SimpleBuilder extends StatefulWidget {
88 _SimpleBuilderState createState() => _SimpleBuilderState(); 88 _SimpleBuilderState createState() => _SimpleBuilderState();
89 } 89 }
90 90
91 -class _SimpleBuilderState extends State<SimpleBuilder> {  
92 - final HashSet<Disposer> disposers = HashSet<Disposer>(); 91 +class _SimpleBuilderState extends State<SimpleBuilder>
  92 + with GetStateUpdaterMixin {
  93 + final HashSet<VoidCallback> disposers = HashSet<VoidCallback>();
93 94
94 @override 95 @override
95 void dispose() { 96 void dispose() {
@@ -101,9 +102,10 @@ class _SimpleBuilderState extends State<SimpleBuilder> { @@ -101,9 +102,10 @@ class _SimpleBuilderState extends State<SimpleBuilder> {
101 102
102 @override 103 @override
103 Widget build(BuildContext context) { 104 Widget build(BuildContext context) {
  105 +
104 return TaskManager.instance.exchange( 106 return TaskManager.instance.exchange(
105 disposers, 107 disposers,
106 - setState, 108 + getUpdate,
107 widget.builder, 109 widget.builder,
108 context, 110 context,
109 ); 111 );
@@ -117,10 +119,15 @@ class TaskManager { @@ -117,10 +119,15 @@ class TaskManager {
117 119
118 static TaskManager get instance => _instance ??= TaskManager._(); 120 static TaskManager get instance => _instance ??= TaskManager._();
119 121
120 - StateSetter _setter;  
121 - HashSet<Disposer> _remove; 122 +// StateSetter _setter;//<old>
  123 + GetStateUpdate _setter;
  124 +
  125 +
  126 + HashSet<VoidCallback> _remove;
  127 +
  128 +// void notify(HashSet<StateSetter> _updaters) { //<old>
  129 + void notify(HashSet<GetStateUpdate> _updaters) {
122 130
123 - void notify(HashSet<StateSetter> _updaters) {  
124 if (_setter != null) { 131 if (_setter != null) {
125 if (!_updaters.contains(_setter)) { 132 if (!_updaters.contains(_setter)) {
126 _updaters.add(_setter); 133 _updaters.add(_setter);
@@ -130,8 +137,9 @@ class TaskManager { @@ -130,8 +137,9 @@ class TaskManager {
130 } 137 }
131 138
132 Widget exchange( 139 Widget exchange(
133 - HashSet<Disposer> disposers,  
134 - StateSetter setState, 140 + HashSet<VoidCallback> disposers,
  141 +// StateSetter setState, //<old>
  142 + GetStateUpdate setState,
135 Widget Function(BuildContext) builder, 143 Widget Function(BuildContext) builder,
136 BuildContext context, 144 BuildContext context,
137 ) { 145 ) {