Committed by
GitHub
Merge pull request #574 from roipeker/rx_changes
subtle Rx improvements and docs.
Showing
1 changed file
with
54 additions
and
1 deletions
| @@ -3,8 +3,12 @@ import 'dart:collection'; | @@ -3,8 +3,12 @@ import 'dart:collection'; | ||
| 3 | 3 | ||
| 4 | import '../rx_core/rx_interface.dart'; | 4 | import '../rx_core/rx_interface.dart'; |
| 5 | 5 | ||
| 6 | + | ||
| 7 | +/// global object that registers against `GetX` and `Obx`, and allows the reactivity | ||
| 8 | +/// of those `Widgets` and Rx values. | ||
| 6 | RxInterface getObs; | 9 | RxInterface getObs; |
| 7 | 10 | ||
| 11 | +/// Base Rx class that manages all the stream logic for any Type. | ||
| 8 | class _RxImpl<T> implements RxInterface<T> { | 12 | class _RxImpl<T> implements RxInterface<T> { |
| 9 | StreamController<T> subject = StreamController<T>.broadcast(); | 13 | StreamController<T> subject = StreamController<T>.broadcast(); |
| 10 | final _subscriptions = HashMap<Stream<T>, StreamSubscription>(); | 14 | final _subscriptions = HashMap<Stream<T>, StreamSubscription>(); |
| @@ -17,7 +21,7 @@ class _RxImpl<T> implements RxInterface<T> { | @@ -17,7 +21,7 @@ class _RxImpl<T> implements RxInterface<T> { | ||
| 17 | /// Example: | 21 | /// Example: |
| 18 | /// ``` | 22 | /// ``` |
| 19 | /// var counter = 0.obs ; | 23 | /// var counter = 0.obs ; |
| 20 | - /// counter >>= 3; // same as counter.value=3; | 24 | + /// counter <<= 3; // same as counter.value=3; |
| 21 | /// print(counter); // calls .toString() now | 25 | /// print(counter); // calls .toString() now |
| 22 | /// ``` | 26 | /// ``` |
| 23 | /// | 27 | /// |
| @@ -99,11 +103,27 @@ class _RxImpl<T> implements RxInterface<T> { | @@ -99,11 +103,27 @@ class _RxImpl<T> implements RxInterface<T> { | ||
| 99 | subject.add(_value); | 103 | subject.add(_value); |
| 100 | } | 104 | } |
| 101 | 105 | ||
| 106 | + /// updates the value to [null] and adds it to the Stream. | ||
| 107 | + /// Even with null-safety coming, is still an important feature to support, as | ||
| 108 | + /// [call()] doesn't accept [null] values. For instance, | ||
| 109 | + /// [InputDecoration.errorText] has to be null to not show the "error state". | ||
| 110 | + /// | ||
| 111 | + /// Sample: | ||
| 112 | + /// ``` | ||
| 113 | + /// final inputError = ''.obs..nil(); | ||
| 114 | + /// print('${inputError.runtimeType}: $inputError'); // outputs > RxString: null | ||
| 115 | + /// ``` | ||
| 116 | + void nil() { | ||
| 117 | + subject.add(_value=null); | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + /// Same as `toString()` but using a getter. | ||
| 102 | String get string => value.toString(); | 121 | String get string => value.toString(); |
| 103 | 122 | ||
| 104 | @override | 123 | @override |
| 105 | String toString() => value.toString(); | 124 | String toString() => value.toString(); |
| 106 | 125 | ||
| 126 | + /// Returns the json representation of `value`. | ||
| 107 | dynamic toJson() => value; | 127 | dynamic toJson() => value; |
| 108 | 128 | ||
| 109 | /// This equality override works for _RxImpl instances and the internal | 129 | /// This equality override works for _RxImpl instances and the internal |
| @@ -121,12 +141,15 @@ class _RxImpl<T> implements RxInterface<T> { | @@ -121,12 +141,15 @@ class _RxImpl<T> implements RxInterface<T> { | ||
| 121 | // ignore: avoid_equals_and_hash_code_on_mutable_classes | 141 | // ignore: avoid_equals_and_hash_code_on_mutable_classes |
| 122 | int get hashCode => _value.hashCode; | 142 | int get hashCode => _value.hashCode; |
| 123 | 143 | ||
| 144 | + /// Closes the subscriptions for this Rx, releasing the resources. | ||
| 124 | void close() { | 145 | void close() { |
| 125 | _subscriptions.forEach((observable, subscription) => subscription.cancel()); | 146 | _subscriptions.forEach((observable, subscription) => subscription.cancel()); |
| 126 | _subscriptions.clear(); | 147 | _subscriptions.clear(); |
| 127 | subject.close(); | 148 | subject.close(); |
| 128 | } | 149 | } |
| 129 | 150 | ||
| 151 | + /// This is an internal method. | ||
| 152 | + /// Subscribe to changes on the inner stream. | ||
| 130 | void addListener(Stream<T> rxGetx) { | 153 | void addListener(Stream<T> rxGetx) { |
| 131 | if (_subscriptions.containsKey(rxGetx)) { | 154 | if (_subscriptions.containsKey(rxGetx)) { |
| 132 | return; | 155 | return; |
| @@ -138,6 +161,9 @@ class _RxImpl<T> implements RxInterface<T> { | @@ -138,6 +161,9 @@ class _RxImpl<T> implements RxInterface<T> { | ||
| 138 | 161 | ||
| 139 | bool firstRebuild = true; | 162 | bool firstRebuild = true; |
| 140 | 163 | ||
| 164 | + | ||
| 165 | + /// Updates the [value] and adds it to the stream, updating the observer | ||
| 166 | + /// Widget, only if it's different from the previous value. | ||
| 141 | set value(T val) { | 167 | set value(T val) { |
| 142 | if (_value == val && !firstRebuild) return; | 168 | if (_value == val && !firstRebuild) return; |
| 143 | firstRebuild = false; | 169 | firstRebuild = false; |
| @@ -145,6 +171,7 @@ class _RxImpl<T> implements RxInterface<T> { | @@ -145,6 +171,7 @@ class _RxImpl<T> implements RxInterface<T> { | ||
| 145 | subject.add(_value); | 171 | subject.add(_value); |
| 146 | } | 172 | } |
| 147 | 173 | ||
| 174 | + /// Returns the current [value] | ||
| 148 | T get value { | 175 | T get value { |
| 149 | if (getObs != null) { | 176 | if (getObs != null) { |
| 150 | getObs.addListener(subject.stream); | 177 | getObs.addListener(subject.stream); |
| @@ -158,11 +185,15 @@ class _RxImpl<T> implements RxInterface<T> { | @@ -158,11 +185,15 @@ class _RxImpl<T> implements RxInterface<T> { | ||
| 158 | {Function onError, void Function() onDone, bool cancelOnError}) => | 185 | {Function onError, void Function() onDone, bool cancelOnError}) => |
| 159 | stream.listen(onData, onError: onError, onDone: onDone); | 186 | stream.listen(onData, onError: onError, onDone: onDone); |
| 160 | 187 | ||
| 188 | + | ||
| 189 | + /// Binds an existing stream to this Rx to keep the values in sync. | ||
| 161 | void bindStream(Stream<T> stream) => stream.listen((va) => value = va); | 190 | void bindStream(Stream<T> stream) => stream.listen((va) => value = va); |
| 162 | 191 | ||
| 163 | Stream<R> map<R>(R mapper(T data)) => stream.map(mapper); | 192 | Stream<R> map<R>(R mapper(T data)) => stream.map(mapper); |
| 164 | } | 193 | } |
| 165 | 194 | ||
| 195 | + | ||
| 196 | +/// Rx class for `bool` Type. | ||
| 166 | class RxBool extends _RxImpl<bool> { | 197 | class RxBool extends _RxImpl<bool> { |
| 167 | RxBool([bool initial]) { | 198 | RxBool([bool initial]) { |
| 168 | _value = initial; | 199 | _value = initial; |
| @@ -174,11 +205,20 @@ class RxBool extends _RxImpl<bool> { | @@ -174,11 +205,20 @@ class RxBool extends _RxImpl<bool> { | ||
| 174 | 205 | ||
| 175 | bool operator ^(bool other) => !other == value; | 206 | bool operator ^(bool other) => !other == value; |
| 176 | 207 | ||
| 208 | + /// Toggles the bool [value] between false and true. | ||
| 209 | + /// A shortcut for `flag.value = !flag.value;` | ||
| 210 | + RxBool toggle() { | ||
| 211 | + subject.add(_value = !_value); | ||
| 212 | + return this; | ||
| 213 | + } | ||
| 214 | + | ||
| 177 | String toString() { | 215 | String toString() { |
| 178 | return value ? "true" : "false"; | 216 | return value ? "true" : "false"; |
| 179 | } | 217 | } |
| 180 | } | 218 | } |
| 181 | 219 | ||
| 220 | +/// Base Rx class for `num` Types (`double` and `int`) as mostly share the same | ||
| 221 | +/// operator overload, we centralize the common code here. | ||
| 182 | abstract class _BaseRxNum<T> extends _RxImpl<num> { | 222 | abstract class _BaseRxNum<T> extends _RxImpl<num> { |
| 183 | _BaseRxNum operator +(num val) { | 223 | _BaseRxNum operator +(num val) { |
| 184 | subject.add(_value += val); | 224 | subject.add(_value += val); |
| @@ -216,18 +256,21 @@ abstract class _BaseRxNum<T> extends _RxImpl<num> { | @@ -216,18 +256,21 @@ abstract class _BaseRxNum<T> extends _RxImpl<num> { | ||
| 216 | bool operator >(num other) => _value > other; | 256 | bool operator >(num other) => _value > other; |
| 217 | } | 257 | } |
| 218 | 258 | ||
| 259 | +/// Rx class for `double` Type. | ||
| 219 | class RxDouble extends _BaseRxNum<double> { | 260 | class RxDouble extends _BaseRxNum<double> { |
| 220 | RxDouble([double initial]) { | 261 | RxDouble([double initial]) { |
| 221 | _value = initial; | 262 | _value = initial; |
| 222 | } | 263 | } |
| 223 | } | 264 | } |
| 224 | 265 | ||
| 266 | +/// Rx class for `num` Type. | ||
| 225 | class RxNum extends _BaseRxNum<num> { | 267 | class RxNum extends _BaseRxNum<num> { |
| 226 | RxNum([num initial]) { | 268 | RxNum([num initial]) { |
| 227 | _value = initial; | 269 | _value = initial; |
| 228 | } | 270 | } |
| 229 | } | 271 | } |
| 230 | 272 | ||
| 273 | +/// Rx class for `String` Type. | ||
| 231 | class RxString extends _RxImpl<String> { | 274 | class RxString extends _RxImpl<String> { |
| 232 | RxString([String initial]) { | 275 | RxString([String initial]) { |
| 233 | _value = initial; | 276 | _value = initial; |
| @@ -244,12 +287,17 @@ class RxString extends _RxImpl<String> { | @@ -244,12 +287,17 @@ class RxString extends _RxImpl<String> { | ||
| 244 | } | 287 | } |
| 245 | } | 288 | } |
| 246 | 289 | ||
| 290 | +/// Rx class for `int` Type. | ||
| 247 | class RxInt extends _BaseRxNum<int> { | 291 | class RxInt extends _BaseRxNum<int> { |
| 248 | RxInt([int initial]) { | 292 | RxInt([int initial]) { |
| 249 | _value = initial; | 293 | _value = initial; |
| 250 | } | 294 | } |
| 251 | } | 295 | } |
| 252 | 296 | ||
| 297 | + | ||
| 298 | +/// Foundation class used for custom `Types` outside the common native Dart types. | ||
| 299 | +/// For example, any custom "Model" class, like User().obs will use `Rx` as | ||
| 300 | +/// wrapper. | ||
| 253 | class Rx<T> extends _RxImpl<T> { | 301 | class Rx<T> extends _RxImpl<T> { |
| 254 | Rx([T initial]) { | 302 | Rx([T initial]) { |
| 255 | _value = initial; | 303 | _value = initial; |
| @@ -262,21 +310,26 @@ class Rx<T> extends _RxImpl<T> { | @@ -262,21 +310,26 @@ class Rx<T> extends _RxImpl<T> { | ||
| 262 | } | 310 | } |
| 263 | 311 | ||
| 264 | extension StringExtension on String { | 312 | extension StringExtension on String { |
| 313 | + /// Returns a `RxString` with [this] `String` as initial value. | ||
| 265 | RxString get obs => RxString(this); | 314 | RxString get obs => RxString(this); |
| 266 | } | 315 | } |
| 267 | 316 | ||
| 268 | extension IntExtension on int { | 317 | extension IntExtension on int { |
| 318 | + /// Returns a `RxInt` with [this] `int` as initial value. | ||
| 269 | RxInt get obs => RxInt(this); | 319 | RxInt get obs => RxInt(this); |
| 270 | } | 320 | } |
| 271 | 321 | ||
| 272 | extension DoubleExtension on double { | 322 | extension DoubleExtension on double { |
| 323 | + /// Returns a `RxDouble` with [this] `double` as initial value. | ||
| 273 | RxDouble get obs => RxDouble(this); | 324 | RxDouble get obs => RxDouble(this); |
| 274 | } | 325 | } |
| 275 | 326 | ||
| 276 | extension BoolExtension on bool { | 327 | extension BoolExtension on bool { |
| 328 | + /// Returns a `RxBool` with [this] `bool` as initial value. | ||
| 277 | RxBool get obs => RxBool(this); | 329 | RxBool get obs => RxBool(this); |
| 278 | } | 330 | } |
| 279 | 331 | ||
| 280 | extension RxT<T> on T { | 332 | extension RxT<T> on T { |
| 333 | + /// Returns a `Rx` instace with [this] `T` as initial value. | ||
| 281 | Rx<T> get obs => Rx<T>(this); | 334 | Rx<T> get obs => Rx<T>(this); |
| 282 | } | 335 | } |
-
Please register or login to post a comment