improve framework structure, added StateMixin, prepare to getx 4.0
Showing
13 changed files
with
216 additions
and
290 deletions
@@ -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()); | ||
68 | - } | ||
69 | - } | ||
70 | - } | ||
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); | 41 | + refreshGroup(id); |
90 | } | 42 | } |
91 | } | 43 | } |
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 | } | 44 | } |
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) { |
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,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 { |
-
Please register or login to post a comment