roi peker

improve docs, cleanup workers, added samples, fixed rx operators

... ... @@ -59,55 +59,101 @@ var name = 'Jonatas Borges'.obs;
That's all. It's *that* simple.
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.
From now on, we might refer to this reactive-".obs"(ervables) variables as _Rx_.
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?
No, you don't need a StreamBuilder, but you are right about static classes.
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.
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?
No, you just need to play this variable inside an Obx widget.
This is the **magic of GetX**, thanks to Dart's capabilities.
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 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?
No, you don't need a `StreamBuilder`, but you are right about static classes.
Well, in the view, we usually have a lot of boilerplate when we want to change a specific Widget, that's the Flutter way.
With **GetX** you can also forget about this boilerplate code.
`StreamBuilder( … )`? `initialValue: …`? `builder: …`? Nope, you just need to place this variable inside an `Obx()` Widget.
```dart
Obx (() => Text (controller.name));
```
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?
_What do you need to memorize?_ Only `Obx(() =>`.
You are just passing that Widget through an arrow-function into an `Obx()` (the "Observer" of the _Rx_).
`Obx` is pretty smart, and will only change if the value of `controller.name` changes.
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?**
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.
> So, what if I have 5 _Rx_ (observable) variables within an `Obx`?
GetX only updates the screen when the variable changes on the screen, if the screen remains the same, it will not reconstruct anything.
It will just update when **any** of them changes.
> And if I have 30 variables in a class, when I update one, will it update **all** the variables that are in that class?
Nope, just the **specific Widget** that uses that _Rx_ variable.
So, **GetX** only updates the screen, when the _Rx_ variable changes it's value.
```
final isOpen = false.obs;
// NOTHING will happen... same value.
void onButtonTap() => isOpen.value=false;
```
### Advantages
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.
**GetX()** helps you when you need **granular** control over what's being updated.
If you do not need `unique IDs`, because all your variables will be modified when you perform an action, then use `GetBuilder`,
because it's a Simple State Updater (in blocks, like `setState()`), made in just a few lines of code.
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.
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.
If you need a **powerful** State Manager, you can't go wrong with **GetX**.
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 .
It doesn't work with variables, but __flows__, everything in it are `Streams` under the hood.
You can use _rxDart_ in conjunction with it, because everything are `Streams`,
you can listen the `event` of each "_Rx_ variable",
because everything in it are `Streams`.
Without decorations, you can turn anything into Observable with just a ".obs".
It is literally a _BLoC_ approach, easier than _MobX_, and without code generators or decorations.
You can turn **anything** into an _"Observable"_ with just a `.obs`.
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.
### Maximum performance:
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.
In addition to having a smart algorithm for minimal rebuilds, **GetX** uses comparators
to make sure the State has changed.
If you experience any errors in your app, and send a duplicate change of State,
**GetX** will ensure it will not crash.
With **GetX** the State only changes if the `value` change.
That's the main difference between **GetX**, and using _`computed` from MobX_.
When joining two __observables__, and one changes; the listener of that _observable_ will change as well.
With **GetX**, if you join two variables, `GetX()` (similar to `Observer()`) will only rebuild if it implies a real change of State.
### Declaring a reactive variable
You have 3 ways to turn a variable into an observable.
The first is using Rx{Type}.
You have 3 ways to turn a variable into an "observable".
1 - The first is using **`Rx{Type}`**.
```dart
// initial value is recommended but not mandatory
// initial value is recommended, but not mandatory
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final number = RxNum(0)
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
The second is to use Rx and type it with `Rx<Type>`
2 - The second is to use **`Rx`** and use Darts Generics, `Rx<Type>`
```dart
final name = Rx<String>('');
... ... @@ -122,7 +168,7 @@ final myMap = Rx<Map<String, int>>({});
final user = Rx<User>();
```
The third, more practical and easier approach, is just to add an .obs to your variable.
3 - The third, more practical, easier and preferred approach, just add **`.obs`** as a property of your `value`:
```dart
final name = ''.obs;
... ... @@ -137,9 +183,16 @@ final myMap = <String, int>{}.obs;
final user = User().obs;
```
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.
##### Having a reactive state, is easy.
As we know, _Dart_ is now heading towards _null safety_.
To be prepared, from now on, you should always start your _Rx_ variables with an **initial value**.
> Transforming a variable into an _observable_ + _initial value_ with **GetX** 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`, well, will be the _initial value_).
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
### Using the values in the view
... ... @@ -172,22 +225,31 @@ GetX<Controller>(
),
```
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.
If we increment `count1.value++`, it will print:
- `count 1 rebuild`
- `count 3 rebuild`
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.
because `count1` has a value of `1`, and `1 + 0 = 1`, changing the `sum` getter value.
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.
If we change `count2.value++`, it will print:
- `count 2 rebuild`
- `count 3 rebuild`
because `count2.value` changed, and the result of the `sum` is now `2`.
- NOTE: By default, the very first event will rebuild the widget, even if it is the same `value`.
This behavior exists due to Boolean variables.
- 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.
Imagine you did this:
```dart
var isLogged = false.obs;
```
and then you check if a user is logged in to trigger an event in "ever".
And then, you checked if a user is "logged in" to trigger an event in `ever`.
```dart
@override
onInit(){
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
... ... @@ -202,7 +264,10 @@ fireRoute(logged) {
}
```
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.
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 contains the same `.value`.
You can remove this behavior if you want, using:
`isLogged.firstRebuild = false;`
... ... @@ -311,13 +376,13 @@ The "assignAll" api will clear the existing list and add any iterable objects th
### Why i have to use .value
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.
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.
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.
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.
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.
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.
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.
... ... @@ -333,7 +398,7 @@ Obviously, if you don't use a type, you will need to have an instance of your co
Workers will assist you, triggering specific callbacks when an event occurs.
```dart
/// Called every time the variable $_ is changed
/// Called every time `count1` changes.
ever(count1, (_) => print("$_ has been changed"));
/// Called only first time the variable $_ is changed
... ... @@ -345,17 +410,25 @@ debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// Ignore all changes within 1 second.
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
All workers (except `debounce`) have a `condition` named parameter, which can be a `bool` or a callback that returns a `bool`.
This `condition` defines when the `callback` function executes.
All workers returns a `Worker` instance, that you can use to cancel ( via `dispose()` ) the worker.
- **`ever`**
is called every time the _Rx_ variable emits a new value.
- **`everAll`**
Much like `ever`, but it takes a `List` of _Rx_ values Called every time its variable is changed. That's it.
- ever
'ever' is called every time its variable is changed. That's it.
- ever
- **`once`**
'once' is called only the first time the variable has been changed.
- debounce
- **`debounce`**
'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.
- interval
- **`interval`**
'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.
## Simple State Manager
... ...
... ... @@ -24,7 +24,7 @@ class _RxImpl<T> implements RxInterface<T> {
///
/// WARNING: still WIP, needs testing!
_RxImpl<T> operator <<(T val) {
subject.add(value = val);
subject.add(_value = val);
return this;
}
... ... @@ -93,9 +93,9 @@ class _RxImpl<T> implements RxInterface<T> {
/// });
/// print( person );
/// ```
void update(void fn(T value)) {
fn(value);
subject.add(value);
void update(void fn(T val)) {
fn(_value);
subject.add(_value);
}
String get string => value.toString();
... ... @@ -175,60 +175,52 @@ class RxBool extends _RxImpl<bool> {
}
}
class RxDouble extends _RxImpl<double> {
RxDouble([double initial]) {
_value = initial;
}
RxDouble operator +(double val) {
subject.add(value += val);
abstract class _BaseRxNum<T> extends _RxImpl<num> {
_BaseRxNum operator +(num val) {
subject.add(_value += val);
return this;
}
RxDouble operator -(double val) {
subject.add(value -= val);
_BaseRxNum operator -(num val) {
subject.add(_value -= val);
return this;
}
RxDouble operator /(double val) {
subject.add(value /= val);
_BaseRxNum operator /(num val) {
subject.add(_value /= val);
return this;
}
RxDouble operator *(double val) {
subject.add(value *= val);
_BaseRxNum operator *(num val) {
subject.add(_value *= val);
return this;
}
}
class RxNum extends _RxImpl<num> {
RxNum([num initial]) {
_value = initial;
}
RxNum operator >>(num val) {
subject.add(value = val);
_BaseRxNum operator ~/(num val) {
subject.add(_value ~/ val);
return this;
}
RxNum operator +(num val) {
subject.add(value += val);
_BaseRxNum operator %(num val) {
subject.add(_value % val);
return this;
}
RxNum operator -(num val) {
subject.add(value -= val);
return this;
}
bool operator <=(num other) => _value <= other;
bool operator >=(num other) => _value >= other;
bool operator <(num other) => _value < other;
bool operator >(num other) => _value > other;
}
RxNum operator /(num val) {
subject.add(value /= val);
return this;
class RxDouble extends _BaseRxNum<double> {
RxDouble([double initial]) {
_value = initial;
}
}
RxNum operator *(num val) {
subject.add(value *= val);
return this;
class RxNum extends _BaseRxNum<num> {
RxNum([num initial]) {
_value = initial;
}
}
... ... @@ -237,51 +229,21 @@ class RxString extends _RxImpl<String> {
_value = initial;
}
RxString operator >>(String val) {
subject.add(value = val);
return this;
}
RxString operator +(String val) {
subject.add(value += val);
subject.add(_value += val);
return this;
}
RxString operator *(int val) {
subject.add(value *= val);
subject.add(_value *= val);
return this;
}
}
class RxInt extends _RxImpl<int> {
class RxInt extends _BaseRxNum<int> {
RxInt([int initial]) {
_value = initial;
}
RxInt operator >>(int val) {
subject.add(value = val);
return this;
}
RxInt operator +(int val) {
subject.add(value += val);
return this;
}
RxInt operator -(int val) {
subject.add(value -= val);
return this;
}
RxInt operator /(int val) {
subject.add(value ~/= val);
return this;
}
RxInt operator *(int val) {
subject.add(value *= val);
return this;
}
}
class Rx<T> extends _RxImpl<T> {
... ...
import 'dart:async';
import 'package:get/get.dart';
import '../rx_core/rx_interface.dart';
import 'utils/debouncer.dart';
bool _conditional(dynamic condition) {
if (condition == null) return true;
if (condition is bool) return condition;
if (condition is bool Function()) return condition();
return true;
}
///
/// Called every time [listener] changes. As long as the [condition] returns true.
///
/// Sample:
/// Every time increment() is called, ever() will process the [condition]
/// (can be a [bool] expression or a [bool Function()]), and only call the callback
/// when [condition] is true.
/// In our case, only when count is bigger to 5. In order to "dispose" this Worker
/// that will run forever, we made a [worker] variable. So, when the count value
/// reaches 10, the worker gets disposed, and releases any memory resources.
///
/// ```
/// // imagine some counter widget...
///
/// class _CountController extends GetxController {
/// final count = 0.obs;
/// Worker worker;
///
/// void onInit() {
/// worker = ever(count, (value) {
/// print('counter changed to: $value');
/// if (value == 10) worker.dispose();
/// }, condition: () => count > 5);
/// }
///
/// void increment() => count + 1;
/// }
/// ```
Worker ever<T>(RxInterface<T> listener, Function(T) callback,
{bool condition = true}) {
{dynamic condition = true}) {
StreamSubscription sub = listener.subject.stream.listen((event) {
if (condition) callback(event);
if (_conditional(condition)) callback(event);
});
Future<void> cancel() {
return sub.cancel();
}
return Worker(cancel, '[ever]');
return Worker(sub.cancel, '[ever]');
}
Worker everAll(List<RxInterface> listener, Function(dynamic) callback,
{bool condition = true}) {
/// Similar to [ever], but takes a list of [listeners], the condition for the [callback]
/// is common to all [listeners], and the [callback] is executed to each one of them.
/// The [Worker] is common to all, so [worker.dispose()] will cancel all streams.
Worker everAll(List<RxInterface> listeners, Function(dynamic) callback,
{dynamic condition = true}) {
List<StreamSubscription> evers = <StreamSubscription>[];
for (var i in listener) {
for (var i in listeners) {
StreamSubscription sub = i.subject.stream.listen((event) {
if (condition) callback(event);
if (_conditional(condition)) callback(event);
});
evers.add(sub);
}
Future<void> cancel() {
for (var i in evers) {
i.cancel();
}
for (var i in evers) i.cancel();
return Future.value(() {});
}
return Worker(cancel, '[everAll]');
}
/// [once()] will execute only 1 time when [condition] is met and cancel
/// the subscription to the [listener] stream right after that.
/// [condition] defines when [callback] is called, and
/// can be a [bool] or a [bool Function()].
///
/// Sample:
/// ```
/// class _CountController extends GetxController {
/// final count = 0.obs;
/// Worker worker;
///
/// @override
/// Future<void> onInit() async {
/// worker = once(count, (value) {
/// print("counter reached $value before 3 seconds.");
/// }, condition: () => count() > 2);
/// 3.delay(worker.dispose);
/// }
/// void increment() => count + 1;
/// }
///```
Worker once<T>(RxInterface<T> listener, Function(T) callback,
{bool condition = true}) {
{dynamic condition}) {
Worker ref;
StreamSubscription sub;
int times = 0;
sub = listener.subject.stream.listen((event) {
if (!condition) return null;
times++;
if (times < 2) {
if (!_conditional(condition)) return;
ref._disposed = true;
ref._log('called');
sub?.cancel();
callback(event);
} else {
sub.cancel();
}
});
Future<void> cancel() {
return sub.cancel();
}
return Worker(cancel, '[once]');
ref = Worker(sub.cancel, '[once]');
return ref;
}
/// Ignore all changes in [listener] during [time] (1 sec by default) or until
/// [condition] is met (can be a [bool] expression or a [bool Function()]),
/// It brings the 1st "value" since the period of time, so
/// if you click a counter button 3 times in 1 sec, it will show you "1" (after 1 sec of the first press)
/// click counter 3 times in 1 sec, it will show you "4" (after 1 sec)
/// click counter 2 times in 1 sec, it will show you "7" (after 1 sec).
///
/// Sample:
/// // wait 1 sec each time an event starts, only if counter is lower than 20.
/// worker = interval(
/// count,
/// (value) => print(value),
/// time: 1.seconds,
/// condition: () => count < 20,
/// );
/// ```
Worker interval<T>(RxInterface<T> listener, Function(T) callback,
{Duration time, bool condition = true}) {
{Duration time = const Duration(seconds: 1), dynamic condition = true}) {
bool debounceActive = false;
time ??= const Duration(seconds: 1);
StreamSubscription sub = listener.subject.stream.listen((event) async {
if (debounceActive || !condition) return null;
if (debounceActive || !_conditional(condition)) return;
debounceActive = true;
await Future.delayed(time ?? Duration(seconds: 1));
await Future.delayed(time);
debounceActive = false;
callback(event);
});
Future<void> cancel() {
return sub.cancel();
}
return Worker(cancel, '[interval]');
return Worker(sub?.cancel, '[interval]');
}
/// [debounce] is similar to [interval], but sends the last value.
/// Useful for Anti DDos, every time the user stops typing for 1 second, for instance.
/// When [listener] emits the last "value", when [time] hits, it calls [callback]
/// with the last "value" emitted.
///
/// Sample:
///
/// ```
/// worker = debounce(
/// count,
/// (value) {
/// print(value);
/// if( value > 20 ) worker.dispose();
/// },
/// time: 1.seconds,
/// );
/// }
/// ```
Worker debounce<T>(RxInterface<T> listener, Function(T) callback,
{Duration time}) {
final _debouncer = Debouncer(delay: time ?? Duration(milliseconds: 800));
final _debouncer =
Debouncer(delay: time ?? const Duration(milliseconds: 800));
StreamSubscription sub = listener.subject.stream.listen((event) {
_debouncer(() {
callback(event);
});
});
Future<void> cancel() {
return sub.cancel();
}
return Worker(cancel, '[debounce]');
return Worker(sub.cancel, '[debounce]');
}
class Worker {
Worker(this.worker, this.type);
/// subscription.cancel() callback
final Future<void> Function() worker;
/// type of worker (debounce, interval, ever)..
final String type;
bool _disposed = false;
void _message() {
GetConfig.log('Worker $type disposed');
bool _verbose = true;
void _log(String msg) {
if (!_verbose) return;
GetConfig.log('$runtimeType $type $msg');
}
void dispose() {
worker();
_message();
if (_disposed) {
_log('already disposed');
return;
}
void call() {
_disposed = true;
worker();
_message();
_log('disposed');
}
void call() => dispose();
}
... ...