Jonatas

improve framework structure, added StateMixin, prepare to getx 4.0

@@ -3,43 +3,22 @@ import 'package:get/get.dart'; @@ -3,43 +3,22 @@ import 'package:get/get.dart';
3 import '../../domain/adapters/repository_adapter.dart'; 3 import '../../domain/adapters/repository_adapter.dart';
4 import '../../domain/entity/cases_model.dart'; 4 import '../../domain/entity/cases_model.dart';
5 5
6 -enum Status { loading, success, error }  
7 -  
8 -class HomeController extends GetxController { 6 +class HomeController extends GetxController with StatusMixin<CasesModel> {
9 HomeController({this.homeRepository}); 7 HomeController({this.homeRepository});
10 8
11 /// inject repo abstraction dependency 9 /// inject repo abstraction dependency
12 final IHomeRepository homeRepository; 10 final IHomeRepository homeRepository;
13 11
14 - /// create a reactive status from request with initial value = loading  
15 - final status = Status.loading.obs;  
16 -  
17 - /// create a reactive CasesModel. CasesModel().obs has same result  
18 - final cases = Rx<CasesModel>();  
19 -  
20 /// When the controller is initialized, make the http request 12 /// When the controller is initialized, make the http request
21 @override 13 @override
22 void onInit() { 14 void onInit() {
23 super.onInit(); 15 super.onInit();
24 - fetchDataFromApi();  
25 - }  
26 -  
27 - /// fetch cases from Api  
28 - Future<void> fetchDataFromApi() async {  
29 - /// When the repository returns the value, change the status to success,  
30 - /// and fill in "cases"  
31 - return homeRepository.getCases().then(  
32 - (data) {  
33 - cases(data);  
34 - status(Status.success);  
35 - },  
36 -  
37 - /// In case of error, print the error and change the status  
38 - /// to Status.error  
39 - onError: (err) {  
40 - print("$err");  
41 - return status(Status.error);  
42 - },  
43 - ); 16 + // show loading on start, data on success
  17 + // and error message on error with 0 boilerplate
  18 + homeRepository.getCases().then((data) {
  19 + change(data, status: RxStatus.success());
  20 + }, onError: (err) {
  21 + change(null, status: RxStatus.error(err.toString()));
  22 + });
44 } 23 }
45 } 24 }
@@ -29,9 +29,9 @@ class CountryView extends GetView<HomeController> { @@ -29,9 +29,9 @@ class CountryView extends GetView<HomeController> {
29 ), 29 ),
30 body: Center( 30 body: Center(
31 child: ListView.builder( 31 child: ListView.builder(
32 - itemCount: controller.cases.value.countries.length, 32 + itemCount: controller.value.countries.length,
33 itemBuilder: (context, index) { 33 itemBuilder: (context, index) {
34 - final country = controller.cases.value.countries[index]; 34 + final country = controller.value.countries[index];
35 return ListTile( 35 return ListTile(
36 onTap: () { 36 onTap: () {
37 Get.toNamed('/details', arguments: country); 37 Get.toNamed('/details', arguments: country);
@@ -27,11 +27,8 @@ class HomeView extends GetView<HomeController> { @@ -27,11 +27,8 @@ class HomeView extends GetView<HomeController> {
27 centerTitle: true, 27 centerTitle: true,
28 ), 28 ),
29 body: Center( 29 body: Center(
30 - child: Obx(  
31 - () {  
32 - final status = controller.status.value;  
33 - if (status == Status.loading) return CircularProgressIndicator();  
34 - if (status == Status.error) return Text('Error on connection :('); 30 + child: controller(
  31 + (state) {
35 return Column( 32 return Column(
36 mainAxisAlignment: MainAxisAlignment.center, 33 mainAxisAlignment: MainAxisAlignment.center,
37 children: [ 34 children: [
@@ -45,7 +42,7 @@ class HomeView extends GetView<HomeController> { @@ -45,7 +42,7 @@ class HomeView extends GetView<HomeController> {
45 ), 42 ),
46 ), 43 ),
47 Text( 44 Text(
48 - '${controller.cases.value.global.totalConfirmed}', 45 + '${state.global.totalConfirmed}',
49 style: TextStyle(fontSize: 45, fontWeight: FontWeight.bold), 46 style: TextStyle(fontSize: 45, fontWeight: FontWeight.bold),
50 ), 47 ),
51 SizedBox( 48 SizedBox(
@@ -58,7 +55,7 @@ class HomeView extends GetView<HomeController> { @@ -58,7 +55,7 @@ class HomeView extends GetView<HomeController> {
58 ), 55 ),
59 ), 56 ),
60 Text( 57 Text(
61 - '${controller.cases.value.global.totalDeaths}', 58 + '${state.global.totalDeaths}',
62 style: TextStyle(fontSize: 45, fontWeight: FontWeight.bold), 59 style: TextStyle(fontSize: 45, fontWeight: FontWeight.bold),
63 ), 60 ),
64 SizedBox( 61 SizedBox(
@@ -59,18 +59,18 @@ void main() { @@ -59,18 +59,18 @@ void main() {
59 expect(controller.initialized, true); 59 expect(controller.initialized, true);
60 60
61 /// check initial Status 61 /// check initial Status
62 - expect(controller.status.value, Status.loading); 62 + expect(controller.status.isLoading, true);
63 63
64 /// await time request 64 /// await time request
65 await Future.delayed(Duration(milliseconds: 100)); 65 await Future.delayed(Duration(milliseconds: 100));
66 66
67 - if (controller.status.value == Status.error) {  
68 - expect(controller.cases.value, null); 67 + if (controller.status.isError) {
  68 + expect(controller.value, null);
69 } 69 }
70 70
71 - if (controller.status.value == Status.success) {  
72 - expect(controller.cases.value.global.totalDeaths, 100);  
73 - expect(controller.cases.value.global.totalConfirmed, 200); 71 + if (controller.status.isSuccess) {
  72 + expect(controller.value.global.totalDeaths, 100);
  73 + expect(controller.value.global.totalConfirmed, 200);
74 } 74 }
75 }); 75 });
76 76
1 -import 'package:meta/meta.dart';  
2 import '../../get_core/get_core.dart'; 1 import '../../get_core/get_core.dart';
3 2
4 /// Special callable class to keep the contract of a regular method, and avoid 3 /// Special callable class to keep the contract of a regular method, and avoid
@@ -18,37 +17,34 @@ class _InternalFinalCallback<T> { @@ -18,37 +17,34 @@ class _InternalFinalCallback<T> {
18 /// ```dart 17 /// ```dart
19 /// class SomeController with GetLifeCycle { 18 /// class SomeController with GetLifeCycle {
20 /// SomeController() { 19 /// SomeController() {
21 -/// initLifeCycle(); 20 +/// configureLifeCycle();
22 /// } 21 /// }
23 /// } 22 /// }
24 /// ``` 23 /// ```
25 -mixin GetLifeCycle {  
26 - /// The `initLifeCycle` works as a constructor for the [GetLifeCycle]  
27 - ///  
28 - /// This method must be invoked in the constructor of the implementation  
29 - void initLifeCycle() {  
30 - onStart._callback = _onStart;  
31 - onDelete._callback = _onDelete;  
32 - }  
33 - 24 +mixin GetLifeCycleBase {
34 /// Called at the exact moment the widget is allocated in memory. 25 /// Called at the exact moment the widget is allocated in memory.
35 /// It uses an internal "callable" type, to avoid any @overrides in subclases. 26 /// It uses an internal "callable" type, to avoid any @overrides in subclases.
36 /// This method should be internal and is required to define the 27 /// This method should be internal and is required to define the
37 /// lifetime cycle of the subclass. 28 /// lifetime cycle of the subclass.
38 final onStart = _InternalFinalCallback<void>(); 29 final onStart = _InternalFinalCallback<void>();
39 30
  31 + // /// The `configureLifeCycle` works as a constructor for the [GetLifeCycle]
  32 + // ///
  33 + // /// This method must be invoked in the constructor of the implementation
  34 + // void configureLifeCycle() {
  35 + // if (_initialized) return;
  36 + // }
  37 +
40 /// Internal callback that starts the cycle of this controller. 38 /// Internal callback that starts the cycle of this controller.
41 final onDelete = _InternalFinalCallback<void>(); 39 final onDelete = _InternalFinalCallback<void>();
42 40
43 /// Called immediately after the widget is allocated in memory. 41 /// Called immediately after the widget is allocated in memory.
44 /// You might use this to initialize something for the controller. 42 /// You might use this to initialize something for the controller.
45 - @mustCallSuper  
46 void onInit() {} 43 void onInit() {}
47 44
48 /// Called 1 frame after onInit(). It is the perfect place to enter 45 /// Called 1 frame after onInit(). It is the perfect place to enter
49 /// navigation events, like snackbar, dialogs, or a new route, or 46 /// navigation events, like snackbar, dialogs, or a new route, or
50 /// async request. 47 /// async request.
51 - @mustCallSuper  
52 void onReady() {} 48 void onReady() {}
53 49
54 /// Called before [onDelete] method. [onClose] might be used to 50 /// Called before [onDelete] method. [onClose] might be used to
@@ -57,7 +53,6 @@ mixin GetLifeCycle { @@ -57,7 +53,6 @@ mixin GetLifeCycle {
57 /// Or dispose objects that can potentially create some memory leaks, 53 /// Or dispose objects that can potentially create some memory leaks,
58 /// like TextEditingControllers, AnimationControllers. 54 /// like TextEditingControllers, AnimationControllers.
59 /// Might be useful as well to persist some data on disk. 55 /// Might be useful as well to persist some data on disk.
60 - @mustCallSuper  
61 void onClose() {} 56 void onClose() {}
62 57
63 bool _initialized = false; 58 bool _initialized = false;
@@ -83,6 +78,26 @@ mixin GetLifeCycle { @@ -83,6 +78,26 @@ mixin GetLifeCycle {
83 _isClosed = true; 78 _isClosed = true;
84 onClose(); 79 onClose();
85 } 80 }
  81 +
  82 + void $configureLifeCycle() {
  83 + _checkIfAlreadyConfigured();
  84 + onStart._callback = _onStart;
  85 + onDelete._callback = _onDelete;
  86 + }
  87 +
  88 + void _checkIfAlreadyConfigured() {
  89 + if (_initialized) {
  90 + throw """You can only call configureLifeCycle once.
  91 +The proper place to insert it is in your class's constructor
  92 +that inherits GetLifeCycle.""";
  93 + }
  94 + }
  95 +}
  96 +
  97 +abstract class GetLifeCycle with GetLifeCycleBase {
  98 + GetLifeCycle() {
  99 + $configureLifeCycle();
  100 + }
86 } 101 }
87 102
88 /// Allow track difference between GetxServices and GetxControllers 103 /// Allow track difference between GetxServices and GetxControllers
@@ -10,11 +10,7 @@ import '../../../get_instance/src/lifecycle.dart'; @@ -10,11 +10,7 @@ import '../../../get_instance/src/lifecycle.dart';
10 /// it is Get.reset(). 10 /// it is Get.reset().
11 abstract class GetxService extends DisposableInterface with GetxServiceMixin {} 11 abstract class GetxService extends DisposableInterface with GetxServiceMixin {}
12 12
13 -abstract class DisposableInterface with GetLifeCycle {  
14 - DisposableInterface() {  
15 - initLifeCycle();  
16 - }  
17 - 13 +abstract class DisposableInterface extends GetLifeCycle {
18 /// Called immediately after the widget is allocated in memory. 14 /// Called immediately after the widget is allocated in memory.
19 /// You might use this to initialize something for the controller. 15 /// You might use this to initialize something for the controller.
20 @override 16 @override
@@ -29,7 +25,9 @@ abstract class DisposableInterface with GetLifeCycle { @@ -29,7 +25,9 @@ abstract class DisposableInterface with GetLifeCycle {
29 /// async request. 25 /// async request.
30 @override 26 @override
31 @mustCallSuper 27 @mustCallSuper
32 - void onReady() {} 28 + void onReady() {
  29 + super.onReady();
  30 + }
33 31
34 /// Called before [onDelete] method. [onClose] might be used to 32 /// Called before [onDelete] method. [onClose] might be used to
35 /// dispose resources used by the controller. Like closing events, 33 /// dispose resources used by the controller. Like closing events,
@@ -38,5 +36,7 @@ abstract class DisposableInterface with GetLifeCycle { @@ -38,5 +36,7 @@ abstract class DisposableInterface with GetLifeCycle {
38 /// like TextEditingControllers, AnimationControllers. 36 /// like TextEditingControllers, AnimationControllers.
39 /// Might be useful as well to persist some data on disk. 37 /// Might be useful as well to persist some data on disk.
40 @override 38 @override
41 - void onClose() {} 39 + void onClose() {
  40 + super.onClose();
  41 + }
42 } 42 }
@@ -5,29 +5,77 @@ import '../../../instance_manager.dart'; @@ -5,29 +5,77 @@ import '../../../instance_manager.dart';
5 import '../../get_state_manager.dart'; 5 import '../../get_state_manager.dart';
6 import '../simple/list_notifier.dart'; 6 import '../simple/list_notifier.dart';
7 7
8 -class Value<T> extends ListNotifier implements ValueListenable<T> {  
9 - Value(this._value); 8 +mixin StatusMixin<T> on ListNotifier {
  9 + T _value;
  10 + RxStatus _status;
  11 +
  12 + bool _isNullOrEmpty(dynamic val) {
  13 + if (val == null) return true;
  14 + var result = false;
  15 + if (val is Iterable) {
  16 + result = val.isEmpty;
  17 + } else if (val is String) {
  18 + result = val.isEmpty;
  19 + } else if (val is Map) {
  20 + result = val.isEmpty;
  21 + }
  22 + return result;
  23 + }
  24 +
  25 + void _fillEmptyStatus() {
  26 + _status = _isNullOrEmpty(_value) ? RxStatus.loading() : RxStatus.success();
  27 + }
  28 +
  29 + RxStatus get status {
  30 + notifyChildrens();
  31 + return _status ??= _status = RxStatus.loading();
  32 + }
10 33
11 T get value { 34 T get value {
12 notifyChildrens(); 35 notifyChildrens();
13 return _value; 36 return _value;
14 } 37 }
15 38
16 - @override  
17 - String toString() => value.toString();  
18 -  
19 - T _value;  
20 -  
21 set value(T newValue) { 39 set value(T newValue) {
22 if (_value == newValue) return; 40 if (_value == newValue) return;
23 _value = newValue; 41 _value = newValue;
24 - updater(); 42 + refresh();
  43 + }
  44 +
  45 + @protected
  46 + void change(T newState, {RxStatus status}) {
  47 + var _canUpdate = false;
  48 + if (status != null) {
  49 + _status = status;
  50 + _canUpdate = true;
  51 + }
  52 + if (newState != _value) {
  53 + _value = newState;
  54 + _canUpdate = true;
  55 + }
  56 + if (_canUpdate) {
  57 + refresh();
  58 + }
  59 + }
  60 +}
  61 +
  62 +class Value<T> extends ListNotifier
  63 + with StatusMixin<T>
  64 + implements ValueListenable<T> {
  65 + Value(T val) {
  66 + _value = val;
  67 + _fillEmptyStatus();
25 } 68 }
26 69
27 void update(void fn(T value)) { 70 void update(void fn(T value)) {
28 fn(value); 71 fn(value);
29 - updater(); 72 + refresh();
30 } 73 }
  74 +
  75 + @override
  76 + String toString() => value.toString();
  77 +
  78 + dynamic toJson() => (value as dynamic)?.toJson();
31 } 79 }
32 80
33 extension ReactiveT<T> on T { 81 extension ReactiveT<T> on T {
@@ -36,10 +84,9 @@ extension ReactiveT<T> on T { @@ -36,10 +84,9 @@ extension ReactiveT<T> on T {
36 84
37 typedef Condition = bool Function(); 85 typedef Condition = bool Function();
38 86
39 -abstract class GetNotifier<T> extends Value<T> with GetLifeCycle { 87 +abstract class GetNotifier<T> extends Value<T> with GetLifeCycleBase {
40 GetNotifier(T initial) : super(initial) { 88 GetNotifier(T initial) : super(initial) {
41 - initLifeCycle();  
42 - _fillEmptyStatus(); 89 + $configureLifeCycle();
43 } 90 }
44 91
45 @override 92 @override
@@ -48,32 +95,9 @@ abstract class GetNotifier<T> extends Value<T> with GetLifeCycle { @@ -48,32 +95,9 @@ abstract class GetNotifier<T> extends Value<T> with GetLifeCycle {
48 super.onInit(); 95 super.onInit();
49 SchedulerBinding.instance?.addPostFrameCallback((_) => onReady()); 96 SchedulerBinding.instance?.addPostFrameCallback((_) => onReady());
50 } 97 }
  98 +}
51 99
52 - RxStatus _status;  
53 -  
54 - bool get isNullOrEmpty {  
55 - if (_value == null) return true;  
56 - dynamic val = _value;  
57 - var result = false;  
58 - if (val is Iterable) {  
59 - result = val.isEmpty;  
60 - } else if (val is String) {  
61 - result = val.isEmpty;  
62 - } else if (val is Map) {  
63 - result = val.isEmpty;  
64 - }  
65 - return result;  
66 - }  
67 -  
68 - void _fillEmptyStatus() {  
69 - _status = isNullOrEmpty ? RxStatus.loading() : RxStatus.success();  
70 - }  
71 -  
72 - RxStatus get status {  
73 - notifyChildrens();  
74 - return _status;  
75 - }  
76 - 100 +extension StateExt<T> on StatusMixin<T> {
77 Widget call(NotifierBuilder<T> widget, {Widget onError, Widget onLoading}) { 101 Widget call(NotifierBuilder<T> widget, {Widget onError, Widget onLoading}) {
78 assert(widget != null); 102 assert(widget != null);
79 return SimpleBuilder(builder: (_) { 103 return SimpleBuilder(builder: (_) {
@@ -86,24 +110,6 @@ abstract class GetNotifier<T> extends Value<T> with GetLifeCycle { @@ -86,24 +110,6 @@ abstract class GetNotifier<T> extends Value<T> with GetLifeCycle {
86 } 110 }
87 }); 111 });
88 } 112 }
89 -  
90 - @protected  
91 - void change(T newState, {RxStatus status}) {  
92 - var _canUpdate = false;  
93 - if (status != null) {  
94 - _status = status;  
95 - _canUpdate = true;  
96 - }  
97 - if (newState != _value) {  
98 - _value = newState;  
99 - _canUpdate = true;  
100 - }  
101 - if (_canUpdate) {  
102 - updater();  
103 - }  
104 - }  
105 -  
106 - dynamic toJson() => (value as dynamic)?.toJson();  
107 } 113 }
108 114
109 class RxStatus { 115 class RxStatus {
1 -import 'dart:collection';  
2 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
3 import '../../../get_core/get_core.dart'; 2 import '../../../get_core/get_core.dart';
4 import '../../../get_instance/src/get_instance.dart'; 3 import '../../../get_instance/src/get_instance.dart';
5 import '../../get_state_manager.dart'; 4 import '../../get_state_manager.dart';
6 -  
7 -// Changed to VoidCallback.  
8 -//typedef Disposer = void Function();  
9 -  
10 -// replacing StateSetter, return if the Widget is mounted for extra validation.  
11 -// if it brings overhead the extra call,  
12 -typedef GetStateUpdate = void Function();  
13 -//typedef GetStateUpdate = void Function(VoidCallback fn); 5 +import 'list_notifier.dart';
14 6
15 /// Complies with [GetStateUpdater] 7 /// Complies with [GetStateUpdater]
16 /// 8 ///
@@ -31,14 +23,8 @@ mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> { @@ -31,14 +23,8 @@ mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> {
31 } 23 }
32 } 24 }
33 25
34 -class GetxController extends DisposableInterface {  
35 - final _updaters = <GetStateUpdate>[];  
36 -  
37 -// final _updatersIds = HashMap<String, StateSetter>(); //<old>  
38 - final _updatersIds = HashMap<String, GetStateUpdate>();  
39 -  
40 - final _updatersGroupIds = HashMap<String, List<GetStateUpdate>>();  
41 - 26 +// ignore: prefer_mixin
  27 +class GetxController extends DisposableInterface with ListNotifier {
42 /// Rebuilds [GetBuilder] each time you call [update()]; 28 /// Rebuilds [GetBuilder] each time you call [update()];
43 /// Can take a List of [ids], that will only update the matching 29 /// Can take a List of [ids], that will only update the matching
44 /// `GetBuilder( id: )`, 30 /// `GetBuilder( id: )`,
@@ -49,73 +35,13 @@ class GetxController extends DisposableInterface { @@ -49,73 +35,13 @@ class GetxController extends DisposableInterface {
49 return; 35 return;
50 } 36 }
51 if (ids == null) { 37 if (ids == null) {
52 -// _updaters?.forEach((rs) => rs(() {})); //<old>  
53 - for (final updater in _updaters) {  
54 - updater();  
55 - } 38 + refresh();
56 } else { 39 } else {
57 - // @jonny, remove this commented code if it's not more optimized.  
58 -// for (final id in ids) {  
59 -// if (_updatersIds[id] != null) _updatersIds[id]();  
60 -// if (_updatersGroupIds[id] != null)  
61 -// for (final rs in _updatersGroupIds[id]) rs();  
62 -// }  
63 -  
64 for (final id in ids) { 40 for (final id in ids) {
65 - _updatersIds[id]?.call();  
66 - // ignore: avoid_function_literals_in_foreach_calls  
67 - _updatersGroupIds[id]?.forEach((rs) => rs()); 41 + refreshGroup(id);
68 } 42 }
69 } 43 }
70 } 44 }
71 -  
72 -// VoidCallback addListener(StateSetter listener) {//<old>  
73 - VoidCallback addListener(GetStateUpdate listener) {  
74 - _updaters.add(listener);  
75 - return () => _updaters.remove(listener);  
76 - }  
77 -  
78 -// VoidCallback addListenerId(String key, StateSetter listener) {//<old>  
79 - VoidCallback addListenerId(String key, GetStateUpdate listener) {  
80 -// _printCurrentIds();  
81 - if (_updatersIds.containsKey(key)) {  
82 - _updatersGroupIds[key] ??= <GetStateUpdate>[];  
83 - _updatersGroupIds[key].add(listener);  
84 - return () {  
85 - _updatersGroupIds[key].remove(listener);  
86 - };  
87 - } else {  
88 - _updatersIds[key] = listener;  
89 - return () => _updatersIds.remove(key);  
90 - }  
91 - }  
92 -  
93 - /// To dispose an [id] from future updates(), this ids are registered  
94 - /// by [GetBuilder()] or similar, so is a way to unlink the state change with  
95 - /// the Widget from the Controller.  
96 - void disposeId(String id) {  
97 - _updatersIds.remove(id);  
98 - _updatersGroupIds.remove(id);  
99 - }  
100 -  
101 - /// Remove this after checking the new implementation makes sense.  
102 - /// Uncomment this if you wanna control the removal of ids..  
103 - /// bool _debugging = false;  
104 - /// Future<void> _printCurrentIds() async {  
105 - /// if (_debugging) return;  
106 - /// _debugging = true;  
107 - /// print('about to debug...');  
108 - /// await Future.delayed(Duration(milliseconds: 10));  
109 - /// int totalGroups = 0;  
110 - /// _updatersGroupIds.forEach((key, value) {  
111 - /// totalGroups += value.length;  
112 - /// });  
113 - /// int totalIds = _updatersIds.length;  
114 - /// print(  
115 - /// 'Total: ${totalIds + totalGroups},'+  
116 - /// 'in groups:$totalGroups, solo ids:$totalIds',);  
117 - /// _debugging = false;  
118 - /// }  
119 } 45 }
120 46
121 typedef GetControllerBuilder<T extends DisposableInterface> = Widget Function( 47 typedef GetControllerBuilder<T extends DisposableInterface> = Widget Function(
@@ -187,10 +113,6 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> @@ -187,10 +113,6 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
187 controller?.onStart(); 113 controller?.onStart();
188 } 114 }
189 115
190 - // if (widget.global && Get.smartManagement ==  
191 - //SmartManagement.onlyBuilder) {  
192 - // controller?.onStart();  
193 - // }  
194 _subscribeToController(); 116 _subscribeToController();
195 } 117 }
196 118
@@ -200,20 +122,10 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> @@ -200,20 +122,10 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
200 void _subscribeToController() { 122 void _subscribeToController() {
201 remove?.call(); 123 remove?.call();
202 remove = (widget.id == null) 124 remove = (widget.id == null)
203 -// ? controller?.addListener(setState) //<old>  
204 -// : controller?.addListenerId(widget.id, setState); //<old>  
205 ? controller?.addListener(getUpdate) 125 ? controller?.addListener(getUpdate)
206 : controller?.addListenerId(widget.id, getUpdate); 126 : controller?.addListenerId(widget.id, getUpdate);
207 } 127 }
208 128
209 - /// Sample for [GetStateUpdate] when you don't wanna  
210 - /// use [GetStateHelper mixin].  
211 - /// bool _getUpdater() {  
212 - /// final _mounted = mounted;  
213 - /// if (_mounted) setState(() {});  
214 - /// return _mounted;  
215 - /// }  
216 -  
217 @override 129 @override
218 void dispose() { 130 void dispose() {
219 super.dispose(); 131 super.dispose();
@@ -249,26 +161,6 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>> @@ -249,26 +161,6 @@ class _GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
249 Widget build(BuildContext context) => widget.builder(controller); 161 Widget build(BuildContext context) => widget.builder(controller);
250 } 162 }
251 163
252 -/// This is a experimental feature.  
253 -/// Meant to be used with SimpleBuilder, it auto-registers the variable  
254 -/// like Rx() does with Obx().  
255 -// class Value<T> extends GetxController {  
256 -// Value([this._value]);  
257 -  
258 -// T _value;  
259 -  
260 -// T get value {  
261 -// TaskManager.instance.notify(_updaters);  
262 -// return _value;  
263 -// }  
264 -  
265 -// set value(T newValue) {  
266 -// if (_value == newValue) return;  
267 -// _value = newValue;  
268 -// update();  
269 -// }  
270 -// }  
271 -  
272 /// It's Experimental class, the Api can be change 164 /// It's Experimental class, the Api can be change
273 abstract class GetState<T> extends GetxController { 165 abstract class GetState<T> extends GetxController {
274 GetState(T initialValue) { 166 GetState(T initialValue) {
@@ -146,5 +146,3 @@ @@ -146,5 +146,3 @@
146 // return widget.builder(controller.state); 146 // return widget.builder(controller.state);
147 // } 147 // }
148 //} 148 //}
149 -  
150 -  
  1 +import 'dart:collection';
1 import 'package:flutter/foundation.dart'; 2 import 'package:flutter/foundation.dart';
  3 +import 'package:flutter/widgets.dart';
2 4
3 -import 'simple_builder.dart'; 5 +// This callback remove the listener on addListener function
  6 +typedef Disposer = void Function();
  7 +
  8 +// replacing StateSetter, return if the Widget is mounted for extra validation.
  9 +// if it brings overhead the extra call,
  10 +typedef GetStateUpdate = void Function();
4 11
5 class ListNotifier implements Listenable { 12 class ListNotifier implements Listenable {
6 - List<VoidCallback> _listeners = <VoidCallback>[]; 13 + List<GetStateUpdate> _updaters = <GetStateUpdate>[];
  14 +
  15 + HashMap<String, List<GetStateUpdate>> _updatersGroupIds =
  16 + HashMap<String, List<GetStateUpdate>>();
7 17
8 @protected 18 @protected
9 - void updater() { 19 + void refresh() {
10 assert(_debugAssertNotDisposed()); 20 assert(_debugAssertNotDisposed());
11 - for (var element in _listeners) { 21 + for (var element in _updaters) {
12 element(); 22 element();
13 } 23 }
14 } 24 }
15 25
  26 + @protected
  27 + void refreshGroup(String id) {
  28 + assert(_debugAssertNotDisposed());
  29 + if (_updatersGroupIds.containsKey(id)) {
  30 + for (var item in _updatersGroupIds[id]) {
  31 + item();
  32 + }
  33 + }
  34 + }
  35 +
16 bool _debugAssertNotDisposed() { 36 bool _debugAssertNotDisposed() {
17 assert(() { 37 assert(() {
18 - if (_listeners == null) { 38 + if (_updaters == null) {
19 throw FlutterError('''A $runtimeType was used after being disposed.\n 39 throw FlutterError('''A $runtimeType was used after being disposed.\n
20 'Once you have called dispose() on a $runtimeType, it can no longer be used.'''); 40 'Once you have called dispose() on a $runtimeType, it can no longer be used.''');
21 } 41 }
@@ -26,29 +46,87 @@ class ListNotifier implements Listenable { @@ -26,29 +46,87 @@ class ListNotifier implements Listenable {
26 46
27 @protected 47 @protected
28 void notifyChildrens() { 48 void notifyChildrens() {
29 - TaskManager.instance.notify(_listeners); 49 + TaskManager.instance.notify(_updaters);
30 } 50 }
31 51
32 bool get hasListeners { 52 bool get hasListeners {
33 assert(_debugAssertNotDisposed()); 53 assert(_debugAssertNotDisposed());
34 - return _listeners.isNotEmpty; 54 + return _updaters.isNotEmpty;
35 } 55 }
36 56
37 @override 57 @override
38 - void addListener(VoidCallback listener) { 58 + void removeListener(VoidCallback listener) {
39 assert(_debugAssertNotDisposed()); 59 assert(_debugAssertNotDisposed());
40 - _listeners.add(listener); 60 + _updaters.remove(listener);
41 } 61 }
42 62
43 - @override  
44 - void removeListener(VoidCallback listener) { 63 + void removeListenerId(String id, VoidCallback listener) {
45 assert(_debugAssertNotDisposed()); 64 assert(_debugAssertNotDisposed());
46 - _listeners.remove(listener); 65 + if (_updatersGroupIds.containsKey(id)) {
  66 + _updatersGroupIds[id].remove(listener);
  67 + }
  68 + _updaters.remove(listener);
47 } 69 }
48 70
49 @mustCallSuper 71 @mustCallSuper
50 void dispose() { 72 void dispose() {
51 assert(_debugAssertNotDisposed()); 73 assert(_debugAssertNotDisposed());
52 - _listeners = null; 74 + _updaters = null;
  75 + _updatersGroupIds = null;
  76 + }
  77 +
  78 + @override
  79 + Disposer addListener(GetStateUpdate listener) {
  80 + assert(_debugAssertNotDisposed());
  81 + _updaters.add(listener);
  82 + return () => _updaters.remove(listener);
  83 + }
  84 +
  85 + Disposer addListenerId(String key, GetStateUpdate listener) {
  86 + _updatersGroupIds[key] ??= <GetStateUpdate>[];
  87 + _updatersGroupIds[key].add(listener);
  88 + return () => _updatersGroupIds[key].remove(listener);
  89 + }
  90 +
  91 + /// To dispose an [id] from future updates(), this ids are registered
  92 + /// by [GetBuilder()] or similar, so is a way to unlink the state change with
  93 + /// the Widget from the Controller.
  94 + void disposeId(String id) {
  95 + _updatersGroupIds.remove(id);
  96 + }
  97 +}
  98 +
  99 +class TaskManager {
  100 + TaskManager._();
  101 +
  102 + static TaskManager _instance;
  103 +
  104 + static TaskManager get instance => _instance ??= TaskManager._();
  105 +
  106 + GetStateUpdate _setter;
  107 +
  108 + List<VoidCallback> _remove;
  109 +
  110 + void notify(List<GetStateUpdate> _updaters) {
  111 + if (_setter != null) {
  112 + if (!_updaters.contains(_setter)) {
  113 + _updaters.add(_setter);
  114 + _remove.add(() => _updaters.remove(_setter));
  115 + }
  116 + }
  117 + }
  118 +
  119 + Widget exchange(
  120 + List<VoidCallback> disposers,
  121 + GetStateUpdate setState,
  122 + Widget Function(BuildContext) builder,
  123 + BuildContext context,
  124 + ) {
  125 + _remove = disposers;
  126 + _setter = setState;
  127 + final result = builder(context);
  128 + _remove = null;
  129 + _setter = null;
  130 + return result;
53 } 131 }
54 } 132 }
1 import 'dart:async'; 1 import 'dart:async';
2 import 'package:flutter/widgets.dart'; 2 import 'package:flutter/widgets.dart';
3 import 'get_state.dart'; 3 import 'get_state.dart';
  4 +import 'list_notifier.dart';
4 5
5 typedef ValueBuilderUpdateCallback<T> = void Function(T snapshot); 6 typedef ValueBuilderUpdateCallback<T> = void Function(T snapshot);
6 typedef ValueBuilderBuilder<T> = Widget Function( 7 typedef ValueBuilderBuilder<T> = Widget Function(
@@ -87,7 +88,7 @@ class SimpleBuilder extends StatefulWidget { @@ -87,7 +88,7 @@ class SimpleBuilder extends StatefulWidget {
87 88
88 class _SimpleBuilderState extends State<SimpleBuilder> 89 class _SimpleBuilderState extends State<SimpleBuilder>
89 with GetStateUpdaterMixin { 90 with GetStateUpdaterMixin {
90 - final disposers = <VoidCallback>[]; 91 + final disposers = <Disposer>[];
91 92
92 @override 93 @override
93 void dispose() { 94 void dispose() {
@@ -107,38 +108,3 @@ class _SimpleBuilderState extends State<SimpleBuilder> @@ -107,38 +108,3 @@ class _SimpleBuilderState extends State<SimpleBuilder>
107 ); 108 );
108 } 109 }
109 } 110 }
110 -  
111 -class TaskManager {  
112 - TaskManager._();  
113 -  
114 - static TaskManager _instance;  
115 -  
116 - static TaskManager get instance => _instance ??= TaskManager._();  
117 -  
118 - GetStateUpdate _setter;  
119 -  
120 - List<VoidCallback> _remove;  
121 -  
122 - void notify(List<GetStateUpdate> _updaters) {  
123 - if (_setter != null) {  
124 - if (!_updaters.contains(_setter)) {  
125 - _updaters.add(_setter);  
126 - _remove.add(() => _updaters.remove(_setter));  
127 - }  
128 - }  
129 - }  
130 -  
131 - Widget exchange(  
132 - List<VoidCallback> disposers,  
133 - GetStateUpdate setState,  
134 - Widget Function(BuildContext) builder,  
135 - BuildContext context,  
136 - ) {  
137 - _remove = disposers;  
138 - _setter = setState;  
139 - final result = builder(context);  
140 - _remove = null;  
141 - _setter = null;  
142 - return result;  
143 - }  
144 -}  
@@ -9,7 +9,6 @@ environment: @@ -9,7 +9,6 @@ environment:
9 dependencies: 9 dependencies:
10 flutter: 10 flutter:
11 sdk: flutter 11 sdk: flutter
12 - meta: 1.3.0-nullsafety.3  
13 12
14 dev_dependencies: 13 dev_dependencies:
15 flutter_test: 14 flutter_test:
@@ -9,11 +9,7 @@ class Mock { @@ -9,11 +9,7 @@ class Mock {
9 } 9 }
10 } 10 }
11 11
12 -class DisposableController with GetLifeCycle {  
13 - DisposableController() {  
14 - initLifeCycle();  
15 - }  
16 -} 12 +class DisposableController extends GetLifeCycle {}
17 13
18 // ignore: one_member_abstracts 14 // ignore: one_member_abstracts
19 abstract class Service { 15 abstract class Service {