Last active
May 9, 2022 10:56
-
-
Save ValeriusGC/9a85b831c59041883332dd356b689f2f to your computer and use it in GitHub Desktop.
GetRxDecorator - UDF for Getx variables
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:equatable/equatable.dart'; | |
import 'package:get/get_rx/get_rx.dart'; | |
/// GetRxDecorator for Rx<T> variables in Get library [https://pub.dev/packages/get] | |
/// This wrapper lets to apply UDF concept and makes it easier | |
/// to work with Getx' Rx<T> and Obx. | |
/// | |
/// ============================================================================ | |
/// | |
/// Why one need to use this decorator? Because of problem with Rx<T> variables: | |
/// | |
/// Mainly, direct using this reactive variables violates all of known | |
/// design patterns. Client (some View) send command to change state | |
/// and get result immediately without Model processing. | |
/// | |
/// We should to hide variable itself behind accessors, and this decorator | |
/// makes it in very handy way. | |
/// | |
/// ============================================================================ | |
/// | |
/// Without GetRxDecorator | |
/// | |
/// ```dart | |
/// // declarations: | |
/// | |
/// /// 1. Variable itself. | |
/// final _clickCounterUdf = 0.obs; | |
/// | |
/// /// 2. Getter | |
/// int get clickCounterUdf => _clickCounterUdf(); | |
/// | |
/// /// 3. Setter | |
/// set clickCounterUdf(int v) => _clickCounterUdf(_process(v)); | |
/// | |
/// /// 4. Stream | |
/// Stream<int> get clickCounterStreamUdf => _clickCounterUdf.stream; | |
/// | |
/// /// This processor drops values above 3 down to zero. | |
/// int _process(int v) => v > 3 ? 0 : v; | |
/// | |
/// //======================================================= | |
/// | |
/// // using: | |
/// // Somewhere in View | |
/// return Center( | |
/// child: ElevatedButton( | |
/// child: Obx( | |
/// () => Text('value = ${controller.clickCounterUdf}'), | |
/// ), | |
/// onPressed: () => controller.clickCounterUdf++, | |
/// ), | |
/// ); | |
/// ``` | |
/// | |
/// With GetRxDecorator | |
/// | |
/// ```dart | |
/// | |
/// // declaration: | |
/// | |
/// /// Encapsulated Rx variable | |
/// late var clickCounterDecor = 0.obsDeco(setter: (_, newValue, __) => | |
/// _process(newValue ?? 0)); | |
/// | |
/// //========================================================================= | |
/// | |
/// // using: | |
/// // Somewhere in View | |
/// return Center( | |
/// child: ElevatedButton( | |
/// child: Obx( | |
/// () => Text('value = ${controller.clickCounterDecor}'), | |
/// ), | |
/// onPressed: () => controller.clickCounterDecor++, | |
/// ), | |
/// ); | |
/// | |
/// ``` | |
class GetRxDecorator<T> extends Equatable { | |
GetRxDecorator(T initial, {this.autoRefresh, this.setter}) | |
: _src = Rx<T>(initial); | |
/// Inner .obs variable. | |
final Rx<T> _src; | |
/// Callback for adjust custom setter. | |
/// [oldValue] parameter allows to apply specific algorithms, | |
/// e.g. without [newValue], like a Collatz conjecture (see tests). | |
/// [args] parameter allows using additional arguments in algorithms, | |
/// e.g. type or instance of variable's sender or something (see tests). | |
final GetRxDecoratorSetter<T>? setter; | |
/// Decorates getter. | |
T get value => _src(); | |
/// Decorates setter. | |
set value(T? val) => _setValue(newValue: val); | |
/// Decorates .obs inner stream. | |
Stream<T> get stream => _src.stream; | |
/// Force auto refresh. | |
final bool? autoRefresh; | |
/// Decorates .call([T? value]) but with additional [args] parameter. | |
/// | |
/// Use [this] as functional object with parameters: | |
/// value means new value. Optional. | |
/// args means additional argument(s) for some use cases. Optional. | |
T call([T? value, dynamic args]) { | |
_setValue(newValue: value, args: args); | |
return this.value; | |
} | |
/// Additional setter in cases when no need to change value itself. | |
/// For example, when every call changes inner value only depending | |
/// on external arguments (see Collatz conjecture setter test) | |
T args(dynamic args) => call(_src(), args); | |
/// We can use either custom setter or default setting mechanism. | |
/// | |
/// newValue: value to be set. It may be null in some logic cases. Optional. | |
/// args: optional dynamic parameter. In some cases, you may need | |
/// an additional call context to select the logic of changing a variable. | |
void _setValue({T? newValue, dynamic args}) { | |
// Prepare for adjust latter auto refresh. | |
final isSameValue = newValue == _src(); | |
if (setter == null) { | |
_src(newValue); | |
} else { | |
// Here we can return as `wrong` either [oldValue] or null. | |
final candidate = setter!(value, newValue, args); | |
_src(candidate); | |
} | |
if (isSameValue && (autoRefresh ?? false)) { | |
refresh(); | |
} | |
} | |
void refresh() => _src.refresh(); | |
/// Same as `toString()` but using a getter. | |
String get string => value.toString(); | |
@override | |
String toString() => value.toString(); | |
@override | |
List<Object?> get props => [_src]; | |
} | |
/// Type of callback to change value in UDF manner. | |
/// [oldValue] passes currentValue. | |
/// [newValue] passes value to apply. | |
/// [args] passes additional arguments. | |
typedef GetRxDecoratorSetter<T> = T? Function( | |
T oldValue, T? newValue, dynamic args); | |
//////////////////////////////////////////////////////////////////////////////// | |
/// | |
class GetRxDecoratorInt extends GetRxDecorator<int> { | |
GetRxDecoratorInt(int initial, | |
{bool? autoRefresh, GetRxDecoratorSetter<int>? setter}) | |
: super(initial, autoRefresh: autoRefresh, setter: setter); | |
/// Addition operator. | |
GetRxDecoratorInt operator +(int add) { | |
call(_src.value + add); | |
return this; | |
} | |
/// Subtraction operator. | |
GetRxDecoratorInt operator -(int sub) { | |
call(_src.value - sub); | |
return this; | |
} | |
} | |
/// | |
extension IntGetRxDecoratorX on int { | |
GetRxDecoratorInt obsDeco( | |
{bool? autoRefresh, GetRxDecoratorSetter<int>? setter}) => | |
GetRxDecoratorInt(this, autoRefresh: autoRefresh, setter: setter); | |
} | |
/// | |
class GetRxDecoratorDouble extends GetRxDecorator<double> { | |
GetRxDecoratorDouble(double initial, | |
{bool? autoRefresh, GetRxDecoratorSetter<double>? setter}) | |
: super(initial, autoRefresh: autoRefresh, setter: setter); | |
/// Addition operator. | |
GetRxDecoratorDouble operator +(double add) { | |
call(_src.value + add); | |
return this; | |
} | |
/// Subtraction operator. | |
GetRxDecoratorDouble operator -(double sub) { | |
call(_src.value - sub); | |
return this; | |
} | |
} | |
/// | |
extension DoubleGetRxDecoratorX on double { | |
GetRxDecoratorDouble obsDeco( | |
{bool? autoRefresh, GetRxDecoratorSetter<double>? setter}) => | |
GetRxDecoratorDouble(this, autoRefresh: autoRefresh, setter: setter); | |
} | |
/// | |
class GetRxDecoratorBool extends GetRxDecorator<bool> { | |
GetRxDecoratorBool(bool initial, | |
{bool? autoRefresh, GetRxDecoratorSetter<bool>? setter}) | |
: super(initial, autoRefresh: autoRefresh, setter: setter); | |
GetRxDecoratorBool toggle() { | |
call(_src.value = !_src.value); | |
return this; | |
} | |
} | |
/// | |
extension BoolGetRxDecoratorX on bool { | |
GetRxDecoratorBool obsDeco( | |
{bool? autoRefresh, GetRxDecoratorSetter<bool>? setter}) => | |
GetRxDecoratorBool(this, autoRefresh: autoRefresh, setter: setter); | |
} | |
/// | |
class GetRxDecoratorString extends GetRxDecorator<String> | |
implements Comparable<String>, Pattern { | |
GetRxDecoratorString(String initial, | |
{bool? autoRefresh, GetRxDecoratorSetter<String>? setter}) | |
: super(initial, autoRefresh: autoRefresh, setter: setter); | |
GetRxDecoratorString operator +(String add) { | |
call(_src.value + add); | |
return this; | |
} | |
@override | |
Iterable<Match> allMatches(String string, [int start = 0]) { | |
return _src.value.allMatches(string, start); | |
} | |
@override | |
Match? matchAsPrefix(String string, [int start = 0]) { | |
return _src.value.matchAsPrefix(string, start); | |
} | |
@override | |
int compareTo(String other) { | |
return _src.value.compareTo(other); | |
} | |
} | |
/// | |
extension StringGetRxDecoratorX on String { | |
GetRxDecoratorString obsDeco( | |
{bool? autoRefresh, GetRxDecoratorSetter<String>? setter}) => | |
GetRxDecoratorString(this, autoRefresh: autoRefresh, setter: setter); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void main() { | |
group('GetRxDecorator tests', () { | |
/// | |
test('GetRxDecorator: equality test', () async { | |
{ | |
final v1 = 'a'.obsDeco(); | |
final v2 = 'a'.obsDeco(); | |
expect(v1, equals(v2)); | |
} | |
{ | |
final v1 = GetRxDecorator(const ['a', 'b']); | |
final v2 = GetRxDecorator(const ['a', 'b']); | |
expect(v1, equals(v2)); | |
} | |
{ | |
final v1 = GetRxDecorator(const { | |
1: ['a', 'b'] | |
}); | |
final v2 = GetRxDecorator(const { | |
1: ['a', 'b'] | |
}); | |
expect(v1, equals(v2)); | |
} | |
{ | |
final v1 = 'a'.obsDeco(); | |
final v2 = 'b'.obsDeco(); | |
expect(v1, isNot(equals(v2))); | |
} | |
{ | |
final v1 = GetRxDecorator(const ['a', 'b']); | |
final v2 = GetRxDecorator(const ['a', 'c']); | |
expect(v1, isNot(equals(v2))); | |
} | |
{ | |
final v1 = GetRxDecorator(const { | |
1: ['a', 'b'] | |
}); | |
final v2 = GetRxDecorator(const { | |
1: ['d', 'b'] | |
}); | |
expect(v1, isNot(equals(v2))); | |
} | |
{ | |
final v1 = GetRxDecorator(const { | |
1: ['a', 'b'] | |
}); | |
final v2 = GetRxDecorator(const { | |
2: ['a', 'b'] | |
}); | |
expect(v1, isNot(equals(v2))); | |
} | |
}); | |
/// Here we just use auto setting. | |
test('GetRxDecorator: with default setter test (No special business logic)', | |
() async { | |
var v = 'a'.obsDeco(); | |
expectLater(v.stream, emitsInOrder(['b', 'c', 'cd'])); | |
v('b'); | |
v('c'); | |
v += 'd'; | |
}); | |
/// Here we can use special business logic inside custom setter. | |
test( | |
'GetRxDecorator: with custom setter test (with special business logic)', | |
() async { | |
var v = 'a'.obsDeco( | |
setter: (_, newValue, __) => | |
// We can return as `wrong` either [oldValue] or null. | |
newValue?.contains('-') ?? false ? newValue : null); | |
expectLater(v.stream, emitsInOrder(['b-', '-c', '-c-'])); | |
v('b-'); | |
v('b'); | |
v('c'); | |
v('-c'); | |
v('d'); | |
v += '-'; | |
}); | |
/// Here we does not use autoRefresh. | |
test('GetRxDecorator: without refresh test', () async { | |
final v = 'a'.obsDeco( | |
setter: (_, newValue, __) => | |
// We can return as `wrong` either [oldValue] or null. | |
newValue?.contains('-') ?? false ? newValue : null); | |
expectLater(v.stream, emitsInOrder(['b-', '-c', 'd-d'])); | |
v('b-'); | |
v('b-'); | |
v('b-'); | |
v('b'); | |
v('c'); | |
v('c'); | |
v('-c'); | |
v('-c'); | |
v('-c'); | |
v('d-d'); | |
}); | |
/// Here we use autoRefresh. | |
test('GetRxDecorator: with refresh test', () async { | |
final v = 'a'.obsDeco( | |
autoRefresh: true, | |
// We can return as `wrong` either [oldValue] or null. | |
setter: (_, newValue, __) => | |
newValue?.contains('-') ?? false ? newValue : null, | |
); | |
expectLater(v.stream, emitsInOrder(['b-', 'b-', '-c', '-c', 'd-d'])); | |
v('b-'); | |
v('b-'); | |
v('-c'); | |
v('-c'); | |
v('d-d'); | |
}); | |
/// Here we use [args] as extra variable. | |
test('GetRxDecorator: with args test', () async { | |
final v = 'a'.obsDeco( | |
autoRefresh: true, | |
setter: (_, newValue, args) { | |
if (args is int && args < 2) { | |
return null; | |
} | |
// We can return as `wrong` either [oldValue] or null. | |
return newValue?.contains('-') ?? false ? newValue : null; | |
}, | |
); | |
expectLater(v.stream, emitsInOrder(['b-2', '2-c', '3-d-d'])); | |
v('b-1', 1); | |
v('b-2', 2); | |
v('1-c', 1); | |
v('2-c', 2); | |
v('3-d-d', 3); | |
}); | |
/// Here we use [args] as extra variable - variant 2. | |
test('GetRxDecorator: with args-2 test', () async { | |
final v = 'a'.obsDeco( | |
autoRefresh: true, | |
setter: (oldValue, newValue, args) { | |
if (args is bool) { | |
return null; | |
} | |
// We can return as `wrong` either [oldValue] or null. | |
return newValue?.contains('-') ?? false ? newValue : oldValue; | |
}, | |
); | |
expectLater(v.stream, emitsInOrder(['b-2', '2-c', '3-d-d'])); | |
v('b-1', false); | |
v('b-2', 2); | |
v('1-c', true); | |
v('2-c', 2); | |
v('3-d-d', 4.4); | |
}); | |
/// Test using auto calculate without outer affect ([newValue] == null). | |
test('GetRxDecorator: Collatz conjecture setter test', () async { | |
final v = 7.obsDeco( | |
// Here we use [oldValue] as base of next step | |
// and does not use [newValue] at all. | |
setter: (oldValue, _, __) { | |
if (oldValue.isEven) { | |
return oldValue ~/ 2; | |
} else { | |
return 3 * oldValue + 1; | |
} | |
}, | |
); | |
expectLater( | |
v.stream, | |
emitsInOrder([ | |
22, | |
11, | |
34, | |
17, | |
52, | |
26, | |
13, | |
40, | |
20, | |
10, | |
5, | |
16, | |
8, | |
4, | |
2, | |
1, | |
4, | |
2, | |
1, | |
])); | |
// Just call [v.value()] without outer variables. | |
List.generate(19, (_) => v.call()); | |
}); | |
/// Here one can see overridden operation | |
test('GetRxDecorator: decorate int test', () async { | |
var v = 0.obsDeco(setter: (_, newValue, __) { | |
return (newValue ?? 0) * 2; | |
}); | |
expectLater(v.stream, emitsInOrder([20, 60, 160, 0])); | |
v += 10; | |
v += 10; | |
v += 20; | |
v -= 160; | |
}); | |
/// Here one can see overridden operation | |
test('GetRxDecorator: decorate bool test', () async { | |
var v = true.obsDeco(); | |
expectLater(v.stream, emitsInOrder([false, true])); | |
v.toggle(); | |
v.toggle(); | |
}); | |
}); | |
group('GetRxDecorator as Pattern tests', () { | |
/// | |
test('GetRxDecorator as Pattern: stream test', () async { | |
var rxVarInt = 1.obsDeco(); | |
expectLater(rxVarInt.stream, emitsInOrder([2])); | |
rxVarInt(2); | |
}); | |
/// | |
test('GetRxDecorator as Pattern: value test', () async { | |
var rxVarInt = 1.obsDeco(); | |
expectLater(rxVarInt.stream, emitsInOrder([2, 3, 2, 10])); | |
expect(rxVarInt.value, equals(1)); | |
expect(rxVarInt(), equals(1)); | |
rxVarInt += 1; | |
expect(rxVarInt.value, equals(2)); | |
expect(rxVarInt(), equals(2)); | |
rxVarInt++; | |
expect(rxVarInt.value, equals(3)); | |
expect(rxVarInt(), equals(3)); | |
rxVarInt--; | |
expect(rxVarInt.value, equals(2)); | |
expect(rxVarInt(), equals(2)); | |
rxVarInt(10); | |
expect(rxVarInt.value, equals(10)); | |
expect(rxVarInt(), equals(10)); | |
}); | |
/// | |
test('GetRxDecorator as Pattern: final value test', () async { | |
final rxVarInt = 1.obsDeco(); | |
expectLater(rxVarInt.stream, emitsInOrder([2, 3, 2, 10])); | |
expectLater(rxVarInt.value, equals(1)); | |
expectLater(rxVarInt(), equals(1)); | |
rxVarInt.value += 1; | |
expect(rxVarInt.value, equals(2)); | |
expect(rxVarInt(), equals(2)); | |
rxVarInt.value++; | |
expectLater(rxVarInt.value, equals(3)); | |
expectLater(rxVarInt(), equals(3)); | |
rxVarInt.value--; | |
expectLater(rxVarInt.value, equals(2)); | |
expectLater(rxVarInt(), equals(2)); | |
rxVarInt.value = 10; | |
expectLater(rxVarInt.value, equals(10)); | |
expectLater(rxVarInt(), equals(10)); | |
}); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// This is sample page for [GetRxDecorator] concepts and howto. | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return GetMaterialApp( | |
title: 'Flutter Demo', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: const MyHomePage(), | |
); | |
} | |
} | |
class MyHomePage extends StatelessWidget { | |
const MyHomePage({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: SafeArea( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Text( | |
'Obs and Decorators', | |
style: Theme.of(context).textTheme.displaySmall, | |
textAlign: TextAlign.center, | |
), | |
), | |
GetX<IntController>( | |
init: IntController(), | |
builder: (c) { | |
return Button( | |
c.clickCounterUdf, | |
c.clickCounterDecor, | |
() { | |
c.clickCounterUdf++; | |
c.clickCounterDecor++; | |
}, | |
); | |
}, | |
), | |
GetX<DoubleController>( | |
init: DoubleController(), | |
initState: (_) {}, | |
builder: (c) { | |
return Button( | |
c.valueUdf, | |
c.valueDecor, | |
() { | |
c.valueUdf += 1.1; | |
c.valueDecor += 1.1; | |
}, | |
); | |
}, | |
), | |
GetX<BoolController>( | |
init: BoolController(), | |
initState: (_) {}, | |
builder: (c) { | |
return Button( | |
c.checkedUdf, | |
c.checkedDecor, | |
() { | |
// in plain obs .toggle() does not work in UDF pattern ... | |
c.checkedUdf = !c.checkedUdf; | |
// ... and in decorator it does | |
c.checkedDecor.toggle(); | |
}, | |
); | |
}, | |
), | |
GetX<StringController>( | |
init: StringController(), | |
initState: (_) {}, | |
builder: (c) { | |
return Button( | |
c.stringUdf, | |
c.stringDecor, | |
() { | |
c.stringUdf += ', world!'; | |
c.stringDecor += ', world!'; | |
}, | |
); | |
}, | |
), | |
GetX<CollatzController>( | |
init: CollatzController(), | |
initState: (_) {}, | |
builder: (c) { | |
return Button( | |
c.collatzUdf, | |
c.collatzDecor, | |
() { | |
// In plain obs we re forced to pass some dummy value | |
// an it is confused | |
c.collatzUdf = 10; | |
// whereas the decorator has clear semantics | |
c.collatzDecor(); | |
}, | |
); | |
}, | |
), | |
// This widget demonstrates how to use additions args | |
// in [GetRxDecorator]. | |
// This argument(s) prohibits variable changing. | |
GetX<CollatzController>( | |
builder: (c) { | |
return SizedBox( | |
width: Get.width - 100, | |
child: ElevatedButton( | |
style: ElevatedButton.styleFrom( | |
primary: Colors.red, // background | |
onPrimary: Colors.yellow, // foreground | |
), | |
onPressed: () { | |
c.collatzDecor.args(this); | |
}, | |
child: Column( | |
children: [ | |
Text( | |
'${c.collatzDecor.runtimeType}: ${c.collatzDecor}'), | |
], | |
), | |
), | |
); | |
}, | |
), | |
const Logger(), | |
], | |
), | |
), | |
); | |
} | |
} | |
/// Generic button for checking Decorator concepts. | |
/// We will manipulate with 2 reactive variables at once | |
/// and both should be equal. | |
class Button<O, D, C extends GetxController> extends StatelessWidget { | |
const Button(this.obs, this.obsDecorator, this.onPressed, {Key? key}) | |
: super(key: key); | |
final O obs; | |
final D obsDecorator; | |
final VoidCallback onPressed; | |
@override | |
Widget build(BuildContext context) { | |
return Center( | |
child: SizedBox( | |
width: Get.width - 100, | |
child: ElevatedButton( | |
child: Column( | |
children: [ | |
// Here is standard rx-variable | |
Text((v) { | |
return '${v.runtimeType}: $v'; | |
}(obs)), | |
// Here is decorator for rx-variable | |
Text((v) { | |
return '${v.runtimeType}: $v'; | |
}(obsDecorator)), | |
], | |
), | |
onPressed: onPressed, | |
), | |
), | |
); | |
} | |
} | |
/// Logger demonstrates that decorators work identically to native `.obs`. | |
class Logger extends StatelessWidget { | |
const Logger({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
final ic = Get.find<IntController>(); | |
final dc = Get.find<DoubleController>(); | |
final bc = Get.find<BoolController>(); | |
final sc = Get.find<StringController>(); | |
final cc = Get.find<CollatzController>(); | |
return Obx(() { | |
return SizedBox( | |
width: Get.width - 100, | |
child: Card( | |
color: Theme.of(context).backgroundColor, | |
child: Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: ListTile( | |
title: const Text('Log:'), | |
subtitle: Text('int = ${ic.clickCounterDecor}\n' | |
'double decorator = ${dc.valueDecor},\n' | |
'bool decorator = ${bc.checkedDecor},\n' | |
'string decorator = ${sc.stringDecor},\n' | |
'collatz decorator = ${cc.collatzDecor}'), | |
), | |
), | |
), | |
); | |
}); | |
} | |
} | |
/// | |
class IntController extends GetxController { | |
/// Ordinary observable variable. | |
/// The problem is there is no way to control its value in business layer. | |
var clickCounter = 0.obs; | |
/// Observable variable in UDF pattern. | |
/// Now we can control variable' value through setter [clickCounterUdf]. | |
/// But we have to orchestrate 3 or 4 entity for this pattern: | |
/// | |
/// 1. Variable itself. | |
final _clickCounterUdf = 0.obs; | |
/// 2. Its Getter | |
int get clickCounterUdf => _clickCounterUdf(); | |
/// 3. Its Setter | |
set clickCounterUdf(int v) => _clickCounterUdf(_process(v)); | |
/// 4. Its Stream | |
Stream<int> get clickCounterStreamUdf => _clickCounterUdf.stream; | |
/// Decorator for observable variable. | |
/// All advantages in one. | |
late var clickCounterDecor = | |
0.obsDeco(setter: (_, newValue, __) => _process(newValue ?? 0)); | |
/// This processor drops values above 3 down to zero. | |
int _process(int v) => v > 3 ? 0 : v; | |
} | |
/// | |
class DoubleController extends GetxController { | |
static const _startValue = 0.1; | |
static const _maxValue = 3.1; | |
final _valueUdf = _startValue.obs; | |
double get valueUdf => _valueUdf(); | |
set valueUdf(double v) => _valueUdf(_process(v)); | |
/// | |
late var valueDecor = _startValue.obsDeco( | |
setter: (_, newValue, __) => _process(newValue ?? _startValue)); | |
/// This processor drops values above 3.1 down to 0.1. | |
double _process(double v) => v > _maxValue ? _startValue : v; | |
} | |
/// | |
class BoolController extends GetxController { | |
final _checkedUdf = false.obs; | |
bool get checkedUdf => _checkedUdf(); | |
set checkedUdf(bool v) => _checkedUdf(v); | |
/// Decorator (4-in-1) | |
/// Setter here makes nothing special. | |
var checkedDecor = false.obsDeco(setter: (_, newValue, __) => newValue); | |
} | |
/// | |
class StringController extends GetxController { | |
final _stringUdf = 'hello'.obs; | |
String get stringUdf => _stringUdf(); | |
set stringUdf(String v) => _stringUdf(v.length > 18 ? 'hello' : v); | |
/// Decorator 4-in-1 | |
late var stringDecor = 'hello'.obsDeco(setter: (_, newValue, __) { | |
return _process(newValue ?? ''); | |
}); | |
/// This processor cuts oversized value to simple 'hello' one. | |
String _process(String v) => v.length > 18 ? 'hello' : v; | |
} | |
/// | |
class CollatzController extends GetxController { | |
final _collatzUdf = 7.obs; | |
int get collatzUdf => _collatzUdf(); | |
/// This setter realizes Collatz conjecture. | |
/// See that one forces here to pass something as a parameter | |
/// even if it is not used. | |
set collatzUdf(int _) { | |
if (_collatzUdf.value.isEven) { | |
_collatzUdf.value = _collatzUdf.value ~/ 2; | |
} else { | |
_collatzUdf.value = 3 * _collatzUdf.value + 1; | |
} | |
} | |
/// Decorator. | |
/// This setter realizes Collatz conjecture. | |
/// As you see that we are free to eliminate passing any parameter | |
/// if it is not required. | |
var collatzDecor = 7.obsDeco(setter: (oldValue, _, args) { | |
// See how context works! | |
if (args != null) { | |
Future.delayed(const Duration(milliseconds: 250)).then((_) { | |
ScaffoldMessenger.of(Get.context!).showSnackBar(SnackBar( | |
backgroundColor: Colors.red, | |
content: Text('It is not possible with this args: $args'), | |
)); | |
}); | |
return oldValue; | |
} | |
// | |
if (oldValue.isEven) { | |
return oldValue ~/ 2; | |
} else { | |
return 3 * oldValue + 1; | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment