From 412bf419eae13b6b0c2ed632d41527bbefb6a6a4 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Wed, 13 May 2020 05:25:05 +0200 Subject: [PATCH 01/41] [stacked]-Adds functionality to react to RxLIst --- packages/stacked/CHANGELOG.md | 4 + .../lib/src/reactive_service_mixin.dart | 18 +++-- packages/stacked/pubspec.yaml | 2 +- .../test/reactive_functionality_test.dart | 81 +++++++++++++++++++ 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 packages/stacked/test/reactive_functionality_test.dart diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index 09922c49..0a1c16bd 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.0 + +- Adds functionality to support `RxList` + ## 1.4.3 - Added functionality to save the error thrown by the calling future diff --git a/packages/stacked/lib/src/reactive_service_mixin.dart b/packages/stacked/lib/src/reactive_service_mixin.dart index bfb79183..4c19b46e 100644 --- a/packages/stacked/lib/src/reactive_service_mixin.dart +++ b/packages/stacked/lib/src/reactive_service_mixin.dart @@ -4,13 +4,13 @@ import 'package:observable_ish/observable_ish.dart'; mixin ReactiveServiceMixin { List _listeners = List(); - void listenToReactiveValues(List reactiveValues) { + void listenToReactiveValues(List reactiveValues) { for (var reactiveValue in reactiveValues) { - reactiveValue.values.listen((value) { - for (var listener in _listeners) { - listener(); - } - }); + if (reactiveValue is RxValue) { + reactiveValue.values.listen((value) => _notifyListeners()); + } else if (reactiveValue is RxList) { + reactiveValue.onChange.listen((event) => _notifyListeners()); + } } } @@ -21,4 +21,10 @@ mixin ReactiveServiceMixin { void removeListener(void Function() listener) { _listeners.remove(listener); } + + void _notifyListeners() { + for (var listener in _listeners) { + listener(); + } + } } diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index d11d50e0..c80216a0 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.4.3 +version: 1.5.0 homepage: https://github.com/FilledStacks/stacked environment: diff --git a/packages/stacked/test/reactive_functionality_test.dart b/packages/stacked/test/reactive_functionality_test.dart new file mode 100644 index 00000000..11626121 --- /dev/null +++ b/packages/stacked/test/reactive_functionality_test.dart @@ -0,0 +1,81 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:observable_ish/observable_ish.dart'; +import 'package:stacked/src/reactive_service_mixin.dart'; + +class CounterService with ReactiveServiceMixin { + RxValue _counter = RxValue(initial: 0); + int get counter => _counter.value; + + RxList _counters = RxList(); + RxList get counters => _counters; + + CounterService() { + listenToReactiveValues([ + _counter, + ]); + } + + void updateCounter() { + _counter.value++; + } + + void addCounterToList() { + _counters.add(_counter.value); + } +} + +class MultipleCounterService with ReactiveServiceMixin { + RxList _counters = RxList(); + RxList get counters => _counters; + int _counter = 0; + + MultipleCounterService() { + listenToReactiveValues([ + _counters, + ]); + } + + void addCounterToList() { + _counters.add(_counter++); + } +} + +void main() { + group('Reactive Functionality', () { + test('When RxValue updates on reactive service, should call listeners', + () async { + var called = false; + var reactiveService = CounterService(); + + reactiveService.addListener(() { + called = true; + }); + + reactiveService.updateCounter(); + + // Have to wait for the listener to be called above. In real life the results is not + // expected to happen in the same CPU cycle so this is perfect for a unit test. + await Future.delayed(Duration(milliseconds: 10)); + + expect(called, true); + }); + + test('When RxList updates on reactive service, should call listeners', + () async { + var called = false; + var reactiveService = MultipleCounterService(); + + reactiveService.addListener(() { + called = true; + }); + + reactiveService.addCounterToList(); + + // Have to wait for the listener to be called above. In real life the results is not + // expected to happen in the same CPU cycle so this is perfect for a unit test. + await Future.delayed(Duration(milliseconds: 10)); + + expect(called, true); + }); + }); +} From 8cb5800c93afe0a47bf668eaecfae061ec05db31 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Fri, 15 May 2020 17:53:12 +0200 Subject: [PATCH 02/41] Bumped Get version and fixed the null exception caused by a bug in get --- .../stacked_services/lib/src/navigation_service.dart | 10 +++++++--- packages/stacked_services/pubspec.yaml | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/stacked_services/lib/src/navigation_service.dart b/packages/stacked_services/lib/src/navigation_service.dart index b5307192..bc47e7ee 100644 --- a/packages/stacked_services/lib/src/navigation_service.dart +++ b/packages/stacked_services/lib/src/navigation_service.dart @@ -34,12 +34,16 @@ class NavigationService { NavigationTransition.Cupertino: Transition.cupertino, }; - get navigatorKey => Get.key; + get navigatorKey { + // We construct this instance to make sure the _get value inside the Get package is not null + var tempConstruction = Get(); + return Get.key; + } /// Allows you to configure the default behaviour for navigation. /// /// [defaultTransition] can be set using the static members of [NavigationTransition] - /// + /// /// If you want to use the string directly. Defined [transition] values are /// - fade /// - rightToLeft @@ -72,7 +76,7 @@ class NavigationService { /// of routeName (String). /// /// Defined [transition] values can be accessed as static memebers of [NavigationTransition] - /// + /// /// If you want to use the string directly. Defined [transition] values are /// - fade /// - rightToLeft diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index a87d961a..f45f955a 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: sdk: flutter # navigation - get: ^2.1.0 + get: ^2.5.0 # service_location injectable: ^0.3.0 From 522df6e97a51442a0a37812c8f7002ddab48f057 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Fri, 15 May 2020 17:53:52 +0200 Subject: [PATCH 03/41] Bumped version --- packages/stacked_services/CHANGELOG.md | 4 ++++ packages/stacked_services/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/stacked_services/CHANGELOG.md b/packages/stacked_services/CHANGELOG.md index 9f329f34..502bb5a4 100644 --- a/packages/stacked_services/CHANGELOG.md +++ b/packages/stacked_services/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.4 + +- Fixed a null exception caused by 2.5.0 of get + ## 0.2.3 - Exposed the navigate with transition and replace with transition functionalities diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index f45f955a..14acf55b 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked_services description: A package that contains some default implementations of services required for a cleaner implementation of the Stacked Architecture. -version: 0.2.3 +version: 0.2.4 homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services environment: From 1c7d1f1b741da64fa5f7dc689372c31711df2001 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Fri, 15 May 2020 18:31:13 +0200 Subject: [PATCH 04/41] [stacked] - Makes sure the model is busy as soon as runFuture is called --- packages/stacked/CHANGELOG.md | 4 ++++ packages/stacked/lib/src/base_view_models.dart | 6 ++++++ packages/stacked/pubspec.yaml | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index 0a1c16bd..4f3ffb2d 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.1 + +- Ensure model is busy as soon as runFuture notifyListeners for the first time + ## 1.5.0 - Adds functionality to support `RxList` diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index 9195c553..1e00e20a 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -147,6 +147,9 @@ abstract class FutureViewModel extends _SingleDataSourceViewModel { Future runFuture() async { _hasError = false; _error = null; + // We set busy manually as well because when notify listeners is called to clear error messages the + // ui is rebuilt and if you expect busy to be true it's not. + setBusy(true); notifyListeners(); _data = await runBusyFuture(futureToRun()).catchError((error) { @@ -187,6 +190,9 @@ abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel { Future runFutures() { _futuresCompleter = Completer(); _initialiseData(); + // We set busy manually as well because when notify listeners is called to clear error messages the + // ui is rebuilt and if you expect busy to be true it's not. + setBusy(true); notifyListeners(); for (var key in futuresMap.keys) { diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index c80216a0..46555212 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.5.0 +version: 1.5.1 homepage: https://github.com/FilledStacks/stacked environment: From 27c74e70b16d3c08cbdb48d020ab448d7e344e32 Mon Sep 17 00:00:00 2001 From: Jonny Borges <35742643+jonataslaw@users.noreply.github.com> Date: Sat, 16 May 2020 13:16:30 -0300 Subject: [PATCH 05/41] Fix key is null on Get 2.5.1 --- packages/stacked_services/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index 14acf55b..d0707dc2 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: sdk: flutter # navigation - get: ^2.5.0 + get: ^2.5.2 # service_location injectable: ^0.3.0 From 759f459abdfc43826bd79d0657a90f64e7249801 Mon Sep 17 00:00:00 2001 From: Jonny Borges <35742643+jonataslaw@users.noreply.github.com> Date: Sat, 16 May 2020 13:28:37 -0300 Subject: [PATCH 06/41] Added Web and Desktop Support For web applications, dart: html is used, for mobile, dart: io. Platform.is is from Dart.io and prevents this library from being used in web projects. To correctly identify if a device is android, iOS, desktop or web, and display the default transitions according to the device used, Get created a selective multiplatform wrapper, at run time and compilation, which is GetPlatform, and the API was left exposed for manipulation of navigation services and personalized dialogs by platform, just like this one. Swapping Platform.is for GetPlatform.is I can run the sample project both on the Web, on Mobile, and on the desktop. --- packages/stacked_services/lib/src/dialog_service.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/stacked_services/lib/src/dialog_service.dart b/packages/stacked_services/lib/src/dialog_service.dart index 082c98eb..e7a1857d 100644 --- a/packages/stacked_services/lib/src/dialog_service.dart +++ b/packages/stacked_services/lib/src/dialog_service.dart @@ -1,4 +1,3 @@ -import 'dart:io' show Platform; import 'dart:async'; import 'package:flutter/cupertino.dart'; @@ -151,12 +150,15 @@ class DialogService { /// Logic for deciding which design to use :) if (showDialogForPlatform) { - if (Platform.isAndroid) { + if (GetPlatform.isAndroid) { _materialDesignDialog(); return _dialogCompleter.future; - } else if (Platform.isIOS) { + } else if (GetPlatform.isIOS) { _cupertinoDesignDialog(); return _dialogCompleter.future; + } else { + _materialDesignDialog(); + return _dialogCompleter.future; } } switch (platformDesignType) { From b9443f52128b54327b23b34fc7600d60ec056a95 Mon Sep 17 00:00:00 2001 From: Jonny Borges <35742643+jonataslaw@users.noreply.github.com> Date: Sat, 16 May 2020 13:32:04 -0300 Subject: [PATCH 07/41] Added web folder if web support is enabled, the option 'chrome' is enabled in the IDE. --- .../stacked_services/example/web/favicon.png | Bin 0 -> 917 bytes .../example/web/icons/Icon-192.png | Bin 0 -> 5292 bytes .../example/web/icons/Icon-512.png | Bin 0 -> 8252 bytes .../stacked_services/example/web/index.html | 33 ++++++++++++++++++ .../example/web/manifest.json | 23 ++++++++++++ 5 files changed, 56 insertions(+) create mode 100644 packages/stacked_services/example/web/favicon.png create mode 100644 packages/stacked_services/example/web/icons/Icon-192.png create mode 100644 packages/stacked_services/example/web/icons/Icon-512.png create mode 100644 packages/stacked_services/example/web/index.html create mode 100644 packages/stacked_services/example/web/manifest.json diff --git a/packages/stacked_services/example/web/favicon.png b/packages/stacked_services/example/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/packages/stacked_services/example/web/icons/Icon-192.png b/packages/stacked_services/example/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/packages/stacked_services/example/web/icons/Icon-512.png b/packages/stacked_services/example/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/packages/stacked_services/example/web/index.html b/packages/stacked_services/example/web/index.html new file mode 100644 index 00000000..9b7a438f --- /dev/null +++ b/packages/stacked_services/example/web/index.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + example + + + + + + + + diff --git a/packages/stacked_services/example/web/manifest.json b/packages/stacked_services/example/web/manifest.json new file mode 100644 index 00000000..8c012917 --- /dev/null +++ b/packages/stacked_services/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} From b8cabe24d0c56815076a5db26785340219c7d901 Mon Sep 17 00:00:00 2001 From: Jonny Borges <35742643+jonataslaw@users.noreply.github.com> Date: Sat, 16 May 2020 13:34:42 -0300 Subject: [PATCH 08/41] Fix Get.key is null on version Get 2.5.2 --- packages/stacked_services/lib/src/navigation_service.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/stacked_services/lib/src/navigation_service.dart b/packages/stacked_services/lib/src/navigation_service.dart index bc47e7ee..b2b7b8ba 100644 --- a/packages/stacked_services/lib/src/navigation_service.dart +++ b/packages/stacked_services/lib/src/navigation_service.dart @@ -35,8 +35,6 @@ class NavigationService { }; get navigatorKey { - // We construct this instance to make sure the _get value inside the Get package is not null - var tempConstruction = Get(); return Get.key; } From a678ec9963c2ec1a762038c960060b36bf87aa49 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Sun, 17 May 2020 10:55:37 +0200 Subject: [PATCH 09/41] Removed a ton of duplicate code from Dialog platform implementation --- .../example/lib/ui/views/home_screen.dart | 46 +++- .../lib/src/dialog_builder.dart | 4 + .../lib/src/dialog_service.dart | 218 ++++++------------ .../lib/src/platform_dialog.dart | 124 ++++++++++ 4 files changed, 241 insertions(+), 151 deletions(-) create mode 100644 packages/stacked_services/lib/src/dialog_builder.dart create mode 100644 packages/stacked_services/lib/src/platform_dialog.dart diff --git a/packages/stacked_services/example/lib/ui/views/home_screen.dart b/packages/stacked_services/example/lib/ui/views/home_screen.dart index 192d6605..fca52a98 100644 --- a/packages/stacked_services/example/lib/ui/views/home_screen.dart +++ b/packages/stacked_services/example/lib/ui/views/home_screen.dart @@ -30,7 +30,7 @@ class _HomeScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - 'Press the button below to show a regular dialog', + 'Press the button below to show a regular Material dialog', softWrap: true, style: TextStyle( fontSize: 14, @@ -44,11 +44,11 @@ class _HomeScreenState extends State { ); }, child: Text( - 'Show Dialog', + 'Show Material Dialog', ), ), Text( - 'Press the button below to show a confirmation dialog', + 'Press the button below to show a Material confirmation dialog', softWrap: true, style: TextStyle( fontSize: 14, @@ -62,7 +62,45 @@ class _HomeScreenState extends State { ); }, child: Text( - 'Show Confirmation Dialog', + 'Show Material Confirmation Dialog', + ), + ), + Text( + 'Press the button below to show a Cupertino dialog', + softWrap: true, + style: TextStyle( + fontSize: 14, + ), + ), + OutlineButton( + onPressed: () async { + await _dialogService.showDialog( + dialogPlatform: DialogPlatform.Cupertino, + title: 'Test Confirmation Dialog Title', + description: 'Test Dialog Description', + ); + }, + child: Text( + 'Show Cupertino Dialog', + ), + ), + Text( + 'Press the button below to show a Cupertino confirmation dialog', + softWrap: true, + style: TextStyle( + fontSize: 14, + ), + ), + OutlineButton( + onPressed: () async { + await _dialogService.showConfirmationDialog( + dialogPlatform: DialogPlatform.Cupertino, + title: 'Test Confirmation Dialog Title', + description: 'Test Confirmation Dialog Description', + ); + }, + child: Text( + 'Show Cupertino Confirmation Dialog', ), ), Text( diff --git a/packages/stacked_services/lib/src/dialog_builder.dart b/packages/stacked_services/lib/src/dialog_builder.dart new file mode 100644 index 00000000..5e08226d --- /dev/null +++ b/packages/stacked_services/lib/src/dialog_builder.dart @@ -0,0 +1,4 @@ +/// Provides functionality to build dialogs +class DialogBuilder { + +} \ No newline at end of file diff --git a/packages/stacked_services/lib/src/dialog_service.dart b/packages/stacked_services/lib/src/dialog_service.dart index 082c98eb..599a3dda 100644 --- a/packages/stacked_services/lib/src/dialog_service.dart +++ b/packages/stacked_services/lib/src/dialog_service.dart @@ -1,14 +1,15 @@ -import 'dart:io' show Platform; import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:injectable/injectable.dart'; +import 'package:stacked_services/src/platform_dialog.dart'; -enum PlatformDesignType { +enum DialogPlatform { Cupertino, Material, + Custom, } /// A DialogService that uses the Get package to show dialogs @@ -20,159 +21,88 @@ class DialogService { // dialogs to be built along with keys. the user should then be able to show the dialog // using that key. - /// Calls the dialog listener and returns a Future that will wait for dialogComplete. - /// if you want it to be a confirmation dialog then you can set `isConfirmationDialog` to `true` + /// Shows a dialog to the user + /// + /// It will show a platform specific dialog by default. This can be changed by setting [dialogPlatform] Future showDialog({ String title, String description, String cancelText, String confirmText = 'Ok', - /// ignored when `showDialogForPlatform` is `true` - /// you must change `showDialogForPlatform` to `false` to use this property - /// providing nothing in here and setting the `showDialogForPlatform` to `false` - /// will result in using material desing all the time - PlatformDesignType platformDesignType, - - /// setting to `false` will not ignore `platform` :) - /// default is `true` which ignores `platform` :) - bool showDialogForPlatform = true, + /// Indicates which [DialogPlatform] to show. + /// + /// When not set a Platform specific dialog will be shown + DialogPlatform dialogPlatform, }) { _dialogCompleter = Completer(); - var isConfirmationDialog = cancelText != null; - /// Dialog Design For Android (Material) - _materialDesignDialog() { - return Get.dialog( - AlertDialog( - titleTextStyle: TextStyle( - color: Colors.black, - ), - contentTextStyle: TextStyle( - color: Colors.black, - ), - title: Text( - title, - ), - content: Text( - description, - ), - actions: [ - if (isConfirmationDialog) - FlatButton( - child: Text( - cancelText, - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - if (!_dialogCompleter.isCompleted) - completeDialog( - DialogResponse( - confirmed: false, - ), - ); - }, - ), - FlatButton( - child: Text( - confirmText, - style: TextStyle(), - ), - onPressed: () { - if (!_dialogCompleter.isCompleted) - completeDialog( - DialogResponse( - confirmed: true, - ), - ); - }, - ), - ], - ), - barrierDismissible: false, - ); + if (dialogPlatform != null) { + _showDialog( + title: title, + description: description, + cancelText: cancelText, + confirmText: confirmText, + dialogPlatform: dialogPlatform); + } else { + var _dialogType = GetPlatform.isAndroid + ? DialogPlatform.Material + : DialogPlatform.Cupertino; + _showDialog( + title: title, + description: description, + cancelText: cancelText, + confirmText: confirmText, + dialogPlatform: _dialogType); } - /// Dialog Design For iOS (Cupertino) - _cupertinoDesignDialog() { - return Get.dialog( - CupertinoAlertDialog( - title: Text( - title, - style: TextStyle( - color: Colors.black, - ), - ), - content: Text( - description, - style: TextStyle( - color: Colors.black, - ), - ), - actions: [ - if (isConfirmationDialog) - CupertinoButton( - child: Text( - cancelText, - style: TextStyle( - color: Colors.red, - ), - ), - onPressed: () { - if (!_dialogCompleter.isCompleted) - completeDialog( - DialogResponse( - confirmed: false, - ), - ); - }, - ), - CupertinoButton( - child: Text( - confirmText, - style: TextStyle(), - ), + return _dialogCompleter.future; + } + + Future _showDialog({ + String title, + String description, + String cancelText, + String confirmText, + DialogPlatform dialogPlatform, + }) { + var isConfirmationDialog = cancelText != null; + return Get.dialog( + PlatformDialog( + dialogPlatform: dialogPlatform, + title: title, + content: description, + actions: [ + if (isConfirmationDialog) + PlatformButton( + dialogPlatform: dialogPlatform, + text: cancelText, + isCancelButton: true, onPressed: () { if (!_dialogCompleter.isCompleted) completeDialog( DialogResponse( - confirmed: true, + confirmed: false, ), ); }, ), - ], - ), - barrierDismissible: false, - ); - } - - /// Logic for deciding which design to use :) - if (showDialogForPlatform) { - if (Platform.isAndroid) { - _materialDesignDialog(); - return _dialogCompleter.future; - } else if (Platform.isIOS) { - _cupertinoDesignDialog(); - return _dialogCompleter.future; - } - } - switch (platformDesignType) { - case PlatformDesignType.Material: - _materialDesignDialog(); - return _dialogCompleter.future; - break; - case PlatformDesignType.Cupertino: - _cupertinoDesignDialog(); - return _dialogCompleter.future; - break; - default: - _materialDesignDialog(); - return _dialogCompleter.future; - break; - } + PlatformButton( + dialogPlatform: dialogPlatform, + text: confirmText, + onPressed: () { + if (!_dialogCompleter.isCompleted) + completeDialog( + DialogResponse( + confirmed: true, + ), + ); + }, + ), + ], + ), + barrierDismissible: false, + ); } /// Shows a confirmation dialog with title and description @@ -182,15 +112,10 @@ class DialogService { String cancelText = 'Cancel', String confirmText = 'Ok', - /// ignored when `showDialogForPlatform` is `true` - /// you must change `showDialogForPlatform` to `false` to use this property - /// providing nothing in here and setting the `showDialogForPlatform` to `false` - /// will result in using material desing all the time - PlatformDesignType platformDesignType, - - /// setting to `false` will not ignore `platform` :) - /// default is `true` which ignores `platform` :) - bool showDialogForPlatform = true, + /// Indicates which [DialogPlatform] to show. + /// + /// When not set a Platform specific dialog will be shown + DialogPlatform dialogPlatform, }) { _dialogCompleter = Completer(); @@ -199,8 +124,7 @@ class DialogService { description: description, confirmText: confirmText, cancelText: cancelText, - platformDesignType: platformDesignType, - showDialogForPlatform: showDialogForPlatform, + dialogPlatform: dialogPlatform, ); return _dialogCompleter.future; diff --git a/packages/stacked_services/lib/src/platform_dialog.dart b/packages/stacked_services/lib/src/platform_dialog.dart new file mode 100644 index 00000000..90bb3860 --- /dev/null +++ b/packages/stacked_services/lib/src/platform_dialog.dart @@ -0,0 +1,124 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked_services/stacked_services.dart'; + +const EdgeInsets _defaultInsetPadding = + EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0); + +const TextStyle _defaultTextStyle = TextStyle(color: Colors.black); +const TextStyle _defaultTitleMaterialStyle = + TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20); +const TextStyle _cancelTextStyle = TextStyle(color: Colors.red); + +class PlatformButton extends StatelessWidget { + final DialogPlatform dialogPlatform; + final String text; + final Function onPressed; + final bool isCancelButton; + + const PlatformButton({ + Key key, + this.dialogPlatform, + this.isCancelButton = false, + @required this.text, + @required this.onPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + switch (dialogPlatform) { + case DialogPlatform.Cupertino: + return CupertinoDialogAction( + child: Text( + text, + style: isCancelButton ? _cancelTextStyle : null, + ), + onPressed: onPressed, + ); + + case DialogPlatform.Material: + default: + return FlatButton( + child: Text(text, + style: isCancelButton ? _cancelTextStyle : _defaultTextStyle), + onPressed: onPressed, + ); + } + } +} + +class PlatformDialog extends StatelessWidget { + /// The title of the dialog is displayed in a large font at the top + final String title; + + /// Padding around the title. + /// + /// If there is no title, no padding will be provided. Otherwise, this padding + /// is used. + /// + /// This property defaults to providing 24 pixels on the top, left, and right + /// of the title. If the [content] is not null, then no bottom padding is + /// provided (but see [contentPadding]). If it _is_ null, then an extra 20 + /// pixels of bottom padding is added to separate the [title] from the + /// [actions]. + final EdgeInsetsGeometry titlePadding; + + /// Style for the text in the [title] of this [AlertDialog]. + final TextStyle titleTextStyle; + + /// The content of the dialog is displayed in the center of the dialog + final String content; + + /// Padding around the content. + + final EdgeInsetsGeometry contentPadding; + + /// Style for the text in the [content] of this [AlertDialog]. + final TextStyle contentTextStyle; + + /// The set of actions that are displayed at the bottom of the + /// dialog. + final List actions; + + final DialogPlatform dialogPlatform; + + final String cancelText; + + const PlatformDialog({ + Key key, + this.title, + this.titlePadding, + this.titleTextStyle = _defaultTextStyle, + this.content, + this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0), + this.contentTextStyle = _defaultTextStyle, + this.actions, + this.dialogPlatform = DialogPlatform.Material, + this.cancelText, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + switch (dialogPlatform) { + case DialogPlatform.Cupertino: + return CupertinoAlertDialog( + title: Text( + title, + ), + content: Text( + content, + ), + actions: actions, + ); + case DialogPlatform.Material: + default: // TODO: When custom dialog registrations are implemented it'll be shown here + return AlertDialog( + titleTextStyle: Theme.of(context).textTheme.headline6, + contentTextStyle: Theme.of(context).textTheme.bodyText1, + title: Text(title), + content: Text(content), + actions: actions, + ); + } + } +} From f04abcd6f8ce951f373cef71dbe543f0f12eb18d Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Sun, 17 May 2020 11:01:06 +0200 Subject: [PATCH 10/41] Moved dialog files into it's own folder to prepare for addition functionality --- packages/stacked_services/lib/src/dialog/dialog_config.dart | 4 ++++ .../stacked_services/lib/src/{ => dialog}/dialog_service.dart | 2 +- .../lib/src/{ => dialog}/platform_dialog.dart | 2 -- packages/stacked_services/lib/src/dialog_builder.dart | 4 ---- packages/stacked_services/lib/stacked_services.dart | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) create mode 100644 packages/stacked_services/lib/src/dialog/dialog_config.dart rename packages/stacked_services/lib/src/{ => dialog}/dialog_service.dart (98%) rename packages/stacked_services/lib/src/{ => dialog}/platform_dialog.dart (96%) delete mode 100644 packages/stacked_services/lib/src/dialog_builder.dart diff --git a/packages/stacked_services/lib/src/dialog/dialog_config.dart b/packages/stacked_services/lib/src/dialog/dialog_config.dart new file mode 100644 index 00000000..11fe5d98 --- /dev/null +++ b/packages/stacked_services/lib/src/dialog/dialog_config.dart @@ -0,0 +1,4 @@ +/// Stores the config / styling for the dialog to use +class DialogConfig { + +} diff --git a/packages/stacked_services/lib/src/dialog_service.dart b/packages/stacked_services/lib/src/dialog/dialog_service.dart similarity index 98% rename from packages/stacked_services/lib/src/dialog_service.dart rename to packages/stacked_services/lib/src/dialog/dialog_service.dart index 599a3dda..177125d0 100644 --- a/packages/stacked_services/lib/src/dialog_service.dart +++ b/packages/stacked_services/lib/src/dialog/dialog_service.dart @@ -4,7 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:injectable/injectable.dart'; -import 'package:stacked_services/src/platform_dialog.dart'; +import 'package:stacked_services/src/dialog/platform_dialog.dart'; enum DialogPlatform { Cupertino, diff --git a/packages/stacked_services/lib/src/platform_dialog.dart b/packages/stacked_services/lib/src/dialog/platform_dialog.dart similarity index 96% rename from packages/stacked_services/lib/src/platform_dialog.dart rename to packages/stacked_services/lib/src/dialog/platform_dialog.dart index 90bb3860..bfc3ded0 100644 --- a/packages/stacked_services/lib/src/platform_dialog.dart +++ b/packages/stacked_services/lib/src/dialog/platform_dialog.dart @@ -6,8 +6,6 @@ const EdgeInsets _defaultInsetPadding = EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0); const TextStyle _defaultTextStyle = TextStyle(color: Colors.black); -const TextStyle _defaultTitleMaterialStyle = - TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20); const TextStyle _cancelTextStyle = TextStyle(color: Colors.red); class PlatformButton extends StatelessWidget { diff --git a/packages/stacked_services/lib/src/dialog_builder.dart b/packages/stacked_services/lib/src/dialog_builder.dart deleted file mode 100644 index 5e08226d..00000000 --- a/packages/stacked_services/lib/src/dialog_builder.dart +++ /dev/null @@ -1,4 +0,0 @@ -/// Provides functionality to build dialogs -class DialogBuilder { - -} \ No newline at end of file diff --git a/packages/stacked_services/lib/stacked_services.dart b/packages/stacked_services/lib/stacked_services.dart index 88f47f7f..0d4f6cc2 100644 --- a/packages/stacked_services/lib/stacked_services.dart +++ b/packages/stacked_services/lib/stacked_services.dart @@ -1,5 +1,5 @@ library stacked_services; export 'src/navigation_service.dart'; -export 'src/dialog_service.dart'; +export 'src/dialog/dialog_service.dart'; export 'src/snackbar_service.dart'; From f52cf0d098b00b5f5a33abf7ddb45ecfb06b8b59 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Sun, 17 May 2020 11:02:35 +0200 Subject: [PATCH 11/41] Version bump --- packages/stacked_services/CHANGELOG.md | 6 ++++++ packages/stacked_services/pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/stacked_services/CHANGELOG.md b/packages/stacked_services/CHANGELOG.md index 502bb5a4..f74c49ae 100644 --- a/packages/stacked_services/CHANGELOG.md +++ b/packages/stacked_services/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.3.0 + +- Dialog Service now shows platform specific dialogs +- Get version bumped +- Services is now Flutter web compatible + ## 0.2.4 - Fixed a null exception caused by 2.5.0 of get diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index d0707dc2..ae2729f1 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked_services description: A package that contains some default implementations of services required for a cleaner implementation of the Stacked Architecture. -version: 0.2.4 +version: 0.3.0 homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services environment: From b812a8e8f89651a18fbbc764b149d6bf9d0a25fd Mon Sep 17 00:00:00 2001 From: Alvin Date: Sun, 17 May 2020 16:06:21 +0700 Subject: [PATCH 12/41] Upgraded injectable package --- packages/stacked_services/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index d0707dc2..fe9716a4 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: # navigation get: ^2.5.2 # service_location - injectable: ^0.3.0 + injectable: ^0.4.0 dev_dependencies: flutter_test: From 1291d490743588bbc7bcb4267e11dd55c029d73e Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Sun, 17 May 2020 11:25:47 +0200 Subject: [PATCH 13/41] version bump --- packages/stacked_services/CHANGELOG.md | 4 ++++ packages/stacked_services/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/stacked_services/CHANGELOG.md b/packages/stacked_services/CHANGELOG.md index f74c49ae..43dfc09a 100644 --- a/packages/stacked_services/CHANGELOG.md +++ b/packages/stacked_services/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.0+1 + +- Injectable version update + ## 0.3.0 - Dialog Service now shows platform specific dialogs diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index 73aae402..fa19af91 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked_services description: A package that contains some default implementations of services required for a cleaner implementation of the Stacked Architecture. -version: 0.3.0 +version: 0.3.0+1 homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services environment: From 293b091484e96764976b876999f83fcaccfdc3ec Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Tue, 19 May 2020 05:46:12 +0200 Subject: [PATCH 14/41] Adds onData callback to the FutureViewModel --- packages/stacked/CHANGELOG.md | 4 ++++ packages/stacked/lib/src/base_view_models.dart | 8 ++++++++ packages/stacked/pubspec.yaml | 2 +- .../stacked/test/futureviewmodel_test.dart | 18 ++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index 4f3ffb2d..73411f42 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.2 + +- Added `onData` function into `FutureViewModel` that can be overridden + ## 1.5.1 - Ensure model is busy as soon as runFuture notifyListeners for the first time diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index 1e00e20a..44bbc223 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -160,10 +160,18 @@ abstract class FutureViewModel extends _SingleDataSourceViewModel { notifyListeners(); }); + if (_data != null) { + onData(_data); + } + changeSource = false; } + /// Called when an error occurs within the future being run void onError(error) {} + + /// Called after the data has been set + void onData(T data) {} } /// Provides functionality for a ViewModel to run and fetch data using multiple future diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index 46555212..b13eccf0 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.5.1 +version: 1.5.2 homepage: https://github.com/FilledStacks/stacked environment: diff --git a/packages/stacked/test/futureviewmodel_test.dart b/packages/stacked/test/futureviewmodel_test.dart index bffe0bc8..6fdb2532 100644 --- a/packages/stacked/test/futureviewmodel_test.dart +++ b/packages/stacked/test/futureviewmodel_test.dart @@ -8,6 +8,7 @@ class TestFutureViewModel extends FutureViewModel { TestFutureViewModel({this.fail = false}); int numberToReturn = 5; + bool dataCalled = false; @override Future futureToRun() async { @@ -15,6 +16,11 @@ class TestFutureViewModel extends FutureViewModel { await Future.delayed(Duration(milliseconds: 20)); return numberToReturn; } + + @override + void onData(int data) { + dataCalled = true; + } } const String NumberDelayFuture = 'delayedNumber'; @@ -84,6 +90,18 @@ void main() { expect(futureViewModel.error.message, _SingleFutureExceptionFailMessage); }); + test('When a future fails onData should not be called', () async { + var futureViewModel = TestFutureViewModel(fail: true); + await futureViewModel.runFuture(); + expect(futureViewModel.dataCalled, false); + }); + + test('When a future passes onData should not called', () async { + var futureViewModel = TestFutureViewModel(); + await futureViewModel.runFuture(); + expect(futureViewModel.dataCalled, true); + }); + group('Dynamic Source Tests', () { test('notifySourceChanged - When called should re-run Future', () async { var futureViewModel = TestFutureViewModel(); From d23ebff73e6252f961885f98c8433d6f8129085a Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Wed, 20 May 2020 11:58:37 +0200 Subject: [PATCH 15/41] Exposed key in all services for single service usage --- packages/stacked_services/CHANGELOG.md | 4 ++++ packages/stacked_services/lib/src/dialog/dialog_service.dart | 4 ++++ packages/stacked_services/lib/src/snackbar_service.dart | 4 ++++ packages/stacked_services/pubspec.yaml | 2 +- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/stacked_services/CHANGELOG.md b/packages/stacked_services/CHANGELOG.md index 43dfc09a..38d78966 100644 --- a/packages/stacked_services/CHANGELOG.md +++ b/packages/stacked_services/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.1 + +- Expose get key through all services in case they are used on their own without navigation service + ## 0.3.0+1 - Injectable version update diff --git a/packages/stacked_services/lib/src/dialog/dialog_service.dart b/packages/stacked_services/lib/src/dialog/dialog_service.dart index 177125d0..a7300a59 100644 --- a/packages/stacked_services/lib/src/dialog/dialog_service.dart +++ b/packages/stacked_services/lib/src/dialog/dialog_service.dart @@ -17,6 +17,10 @@ enum DialogPlatform { class DialogService { Completer _dialogCompleter; + get navigatorKey { + return Get.key; + } + // TODO: Create a dialog UI registration factory that will allow users to register // dialogs to be built along with keys. the user should then be able to show the dialog // using that key. diff --git a/packages/stacked_services/lib/src/snackbar_service.dart b/packages/stacked_services/lib/src/snackbar_service.dart index 07db7abc..c5a20187 100644 --- a/packages/stacked_services/lib/src/snackbar_service.dart +++ b/packages/stacked_services/lib/src/snackbar_service.dart @@ -6,6 +6,10 @@ import 'package:injectable/injectable.dart'; /// A service that allows the user to show the snackbar from a ViewModel @lazySingleton class SnackbarService { + get navigatorKey { + return Get.key; + } + /// Shows a snack bar with the details passed in void showSnackbar({ String title, diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index fa19af91..75cbfbb8 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked_services description: A package that contains some default implementations of services required for a cleaner implementation of the Stacked Architecture. -version: 0.3.0+1 +version: 0.3.1 homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services environment: From 60895bac8e1d386b5c6ca48482d832e3b4fb1a8b Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Wed, 20 May 2020 12:01:50 +0200 Subject: [PATCH 16/41] Fixed some warnings --- .../example/lib/services/thirdparty_services_module.dart | 2 +- packages/stacked_services/example/test/widget_test.dart | 3 --- packages/stacked_services/lib/src/dialog/platform_dialog.dart | 3 --- packages/stacked_services/lib/src/navigation_service.dart | 3 ++- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/stacked_services/example/lib/services/thirdparty_services_module.dart b/packages/stacked_services/example/lib/services/thirdparty_services_module.dart index 975a6124..6f54f6fb 100644 --- a/packages/stacked_services/example/lib/services/thirdparty_services_module.dart +++ b/packages/stacked_services/example/lib/services/thirdparty_services_module.dart @@ -1,7 +1,7 @@ import 'package:injectable/injectable.dart'; import 'package:stacked_services/stacked_services.dart'; -@registerModule +@module abstract class ThirdPartyServicesModule { @lazySingleton NavigationService get navigationService; diff --git a/packages/stacked_services/example/test/widget_test.dart b/packages/stacked_services/example/test/widget_test.dart index 081d39a1..570e0e47 100644 --- a/packages/stacked_services/example/test/widget_test.dart +++ b/packages/stacked_services/example/test/widget_test.dart @@ -5,7 +5,4 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - void main() {} diff --git a/packages/stacked_services/lib/src/dialog/platform_dialog.dart b/packages/stacked_services/lib/src/dialog/platform_dialog.dart index bfc3ded0..0057488f 100644 --- a/packages/stacked_services/lib/src/dialog/platform_dialog.dart +++ b/packages/stacked_services/lib/src/dialog/platform_dialog.dart @@ -2,9 +2,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:stacked_services/stacked_services.dart'; -const EdgeInsets _defaultInsetPadding = - EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0); - const TextStyle _defaultTextStyle = TextStyle(color: Colors.black); const TextStyle _cancelTextStyle = TextStyle(color: Colors.red); diff --git a/packages/stacked_services/lib/src/navigation_service.dart b/packages/stacked_services/lib/src/navigation_service.dart index b2b7b8ba..fe6fb4d4 100644 --- a/packages/stacked_services/lib/src/navigation_service.dart +++ b/packages/stacked_services/lib/src/navigation_service.dart @@ -12,6 +12,7 @@ class NavigationTransition { static const String Rotate = 'rotate'; static const String Size = 'size'; static const String RightToLeftWithFade = 'righttoleftwithfade'; + static const String LeftToRighttWithFade = 'lefttorightwithfade'; static const String Cupertino = 'cupertino'; } @@ -30,7 +31,7 @@ class NavigationService { NavigationTransition.Rotate: Transition.rotate, NavigationTransition.Size: Transition.size, NavigationTransition.RightToLeftWithFade: Transition.rightToLeftWithFade, - NavigationTransition.RightToLeftWithFade: Transition.leftToRightWithFade, + NavigationTransition.LeftToRighttWithFade: Transition.leftToRightWithFade, NavigationTransition.Cupertino: Transition.cupertino, }; From 0e5baf8f79f1450a480749780672845e5af0078e Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Thu, 21 May 2020 07:28:02 +0200 Subject: [PATCH 17/41] Exposes stream subscriptions for pause / resume functionality --- packages/stacked/CHANGELOG.md | 4 ++++ packages/stacked/lib/src/base_view_models.dart | 5 ++++- packages/stacked/pubspec.yaml | 2 +- packages/stacked/test/streamviewmodel_test.dart | 12 +++++++++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index 73411f42..fd5475aa 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.3 + +- Expose subscription for single stream viewmodel and getSubscriptionForKey for multiple stream viewmodel. + ## 1.5.2 - Added `onData` function into `FutureViewModel` that can be overridden diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index 44bbc223..4b7b4005 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -250,6 +250,10 @@ abstract class MultipleStreamViewModel extends _MultiDataSourceViewModel { Map get streamsSubscriptions => _streamsSubscriptions; + /// Returns the stream subscription associated with the key + StreamSubscription getSubscriptionForKey(String key) => + _streamsSubscriptions[key]; + void initialise() { _dataMap = Map(); _errorMap = Map(); @@ -346,7 +350,6 @@ abstract class StreamViewModel extends _SingleDataSourceViewModel /// Stream to listen to Stream get stream; - @visibleForTesting StreamSubscription get streamSubscription => _streamSubscription; StreamSubscription _streamSubscription; diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index b13eccf0..7a9cee1b 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.5.2 +version: 1.5.3 homepage: https://github.com/FilledStacks/stacked environment: diff --git a/packages/stacked/test/streamviewmodel_test.dart b/packages/stacked/test/streamviewmodel_test.dart index 0d7f0519..40846325 100644 --- a/packages/stacked/test/streamviewmodel_test.dart +++ b/packages/stacked/test/streamviewmodel_test.dart @@ -156,7 +156,7 @@ void main() async { () async { var streamViewModel = TestMultipleStreamViewModel(); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 1)); + await Future.delayed(Duration(milliseconds: 4)); expect(streamViewModel.dataMap[_NumberStream], 5); expect(streamViewModel.dataMap[_StringStream], 'five'); }); @@ -202,6 +202,16 @@ void main() async { expect(listenersCalled, true); }); + test( + 'When a stream is initialised should have a subscription for the given key', + () async { + var streamViewModel = TestMultipleStreamViewModel(); + + streamViewModel.initialise(); + expect( + streamViewModel.getSubscriptionForKey(_NumberStream) != null, true); + }); + group('Data Source Change', () { test( 'notifySourceChanged - When called should unsubscribe from original sources', From 3b7f7d99111d4d82865a118125d602273e4b9b73 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Thu, 21 May 2020 17:17:11 +0200 Subject: [PATCH 18/41] Made SpecialtyViewModels reactive viewmodels This allow you to cut down boilerplate termendously when wanting to combine a specialty viewmodel with a reactive service --- packages/stacked/CHANGELOG.md | 6 +- .../stacked/lib/src/base_view_models.dart | 5 +- packages/stacked/pubspec.yaml | 2 +- .../stacked/test/reactive_viewmodel_test.dart | 84 +++++++++++++++++++ 4 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 packages/stacked/test/reactive_viewmodel_test.dart diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index fd5475aa..7268fc88 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,10 +1,14 @@ +## 1.5.4 + +- Made specialty viewmodels reactive as well so you can supply reactive services as an override + ## 1.5.3 - Expose subscription for single stream viewmodel and getSubscriptionForKey for multiple stream viewmodel. ## 1.5.2 -- Added `onData` function into `FutureViewModel` that can be overridden +- Added `onData` function into `FutureViewModel` that can be overridden ## 1.5.1 diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index 4b7b4005..2f06cba7 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -98,11 +98,14 @@ abstract class ReactiveViewModel extends BaseViewModel { } @protected -class DynamicSourceViewModel extends BaseViewModel { +class DynamicSourceViewModel extends ReactiveViewModel { bool changeSource = false; void notifySourceChanged({bool clearOldData = false}) { changeSource = true; } + + @override + List get reactiveServices => []; } class _SingleDataSourceViewModel extends DynamicSourceViewModel { diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index 7a9cee1b..5322d380 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.5.3 +version: 1.5.4 homepage: https://github.com/FilledStacks/stacked environment: diff --git a/packages/stacked/test/reactive_viewmodel_test.dart b/packages/stacked/test/reactive_viewmodel_test.dart new file mode 100644 index 00000000..f2085cdb --- /dev/null +++ b/packages/stacked/test/reactive_viewmodel_test.dart @@ -0,0 +1,84 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:observable_ish/observable_ish.dart'; +import 'package:stacked/src/base_view_models.dart'; +import 'package:stacked/src/reactive_service_mixin.dart'; + +class TestReactiveService with ReactiveServiceMixin { + RxValue _counter = RxValue(initial: 0); + int get counter => _counter.value; + + TestReactiveService() { + listenToReactiveValues([_counter]); + } + + void updateCounter() { + _counter.value++; + } +} + +class TestReactiveViewModel extends ReactiveViewModel { + final _testService = TestReactiveService(); + @override + List get reactiveServices => [_testService]; + + void updateCounter() { + _testService.updateCounter(); + } +} + +class TestFutureReactiveViewModel extends FutureViewModel { + final _testService = TestReactiveService(); + @override + List get reactiveServices => [_testService]; + + @override + Future futureToRun() async { + await Future.delayed(Duration(milliseconds: 5)); + return 1; + } + + void updateCounter() { + _testService.updateCounter(); + } +} + +void main() { + group('ReactiveViewModel Tests -', () { + test( + 'Given a reactive service should notifyListeners when an RX value in it changes', + () async { + var model = TestReactiveViewModel(); + var called = false; + model.addListener(() { + called = true; + }); + model.updateCounter(); + await Future.delayed(Duration(milliseconds: 5)); + expect(called, true); + }); + test( + 'Given a reactive service on FutureViewmodel should notifyListeners when an RX value in it changes', + () async { + var model = TestFutureReactiveViewModel(); + var called = false; + model.addListener(() { + called = true; + }); + model.updateCounter(); + await Future.delayed(Duration(milliseconds: 5)); + expect(called, true); + }); + test('Given a reactive service should not notifyListeners after disposed', + () async { + var model = TestReactiveViewModel(); + var called = false; + model.addListener(() { + called = true; + }); + model.dispose(); + model.updateCounter(); + await Future.delayed(Duration(milliseconds: 5)); + expect(called, false); + }); + }); +} From 2cadde6d5ab1352fb60af553854d51f004927595 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Fri, 22 May 2020 10:04:07 +0200 Subject: [PATCH 19/41] Updated clearAndShow functions --- packages/stacked_services/CHANGELOG.md | 8 ++++++++ .../example/lib/ui/views/first_screen.dart | 8 ++++++++ .../lib/src/navigation_service.dart | 18 +++++++++++++++--- packages/stacked_services/pubspec.yaml | 2 +- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/stacked_services/CHANGELOG.md b/packages/stacked_services/CHANGELOG.md index 38d78966..7a33b7a6 100644 --- a/packages/stacked_services/CHANGELOG.md +++ b/packages/stacked_services/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.3.1+2 + +- When clearing stacking and showing we push instead of replace + +## 0.3.1+1 + +- Provided route as the predicate for the "till" functions + ## 0.3.1 - Expose get key through all services in case they are used on their own without navigation service diff --git a/packages/stacked_services/example/lib/ui/views/first_screen.dart b/packages/stacked_services/example/lib/ui/views/first_screen.dart index e5641abb..aa4de5bd 100644 --- a/packages/stacked_services/example/lib/ui/views/first_screen.dart +++ b/packages/stacked_services/example/lib/ui/views/first_screen.dart @@ -66,6 +66,14 @@ class FirstScreen extends StatelessWidget { ); }, ), + OutlineButton( + child: Text("Clear Till First and Show"), + onPressed: () async { + await _navigationService.clearTillFirstAndShowView( + SecondScreen(), + ); + }, + ), ], ), ), diff --git a/packages/stacked_services/lib/src/navigation_service.dart b/packages/stacked_services/lib/src/navigation_service.dart index fe6fb4d4..21129774 100644 --- a/packages/stacked_services/lib/src/navigation_service.dart +++ b/packages/stacked_services/lib/src/navigation_service.dart @@ -135,6 +135,11 @@ class NavigationService { return Get.toNamed(routeName, arguments: arguments); } + /// Pushes [view] onto the navigation stack + Future navigateToView(Widget view, {dynamic arguments}) { + return Get.to(view, arguments: arguments); + } + /// Replaces the current route with the [routeName] Future replaceWith(String routeName, {dynamic arguments}) { return Get.offNamed(routeName, arguments: arguments); @@ -144,7 +149,7 @@ class NavigationService { Future clearStackAndShow(String routeName, {dynamic arguments}) { _clearBackstackCompletely(); - return replaceWith(routeName, arguments: arguments); + return navigateTo(routeName, arguments: arguments); } /// Pops the navigation stack until there's 1 view left then pushes [routeName] onto the stack @@ -154,6 +159,13 @@ class NavigationService { return navigateTo(routeName, arguments: arguments); } + /// Pops the navigation stack until there's 1 view left then pushes [view] onto the stack + Future clearTillFirstAndShowView(Widget view, {dynamic arguments}) { + _clearBackstackTillFirst(); + + return navigateToView(view); + } + /// Push route and clear stack until predicate is satisfied Future pushNamedAndRemoveUntil(String routeName, {RoutePredicate predicate, arguments, int id}) { @@ -162,11 +174,11 @@ class NavigationService { } void _clearBackstackCompletely() { - navigatorKey.currentState.popUntil((route) => false); + navigatorKey.currentState.popUntil((Route route) => false); } void _clearBackstackTillFirst() { - navigatorKey.currentState.popUntil((route) => route.isFirst); + navigatorKey.currentState.popUntil((Route route) => route.isFirst); } Transition _getTransitionOrDefault(String transition) { diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index 75cbfbb8..1c540813 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked_services description: A package that contains some default implementations of services required for a cleaner implementation of the Stacked Architecture. -version: 0.3.1 +version: 0.3.1+2 homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services environment: From 7a8ff9e9d1eee4e7217119add5f3dd9e884bd72d Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Fri, 22 May 2020 10:11:02 +0200 Subject: [PATCH 20/41] Fixed name changes to line up with function names --- packages/stacked_services/CHANGELOG.md | 4 +++ .../lib/src/dialog/dialog_service.dart | 30 +++++++++---------- packages/stacked_services/pubspec.yaml | 2 +- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/stacked_services/CHANGELOG.md b/packages/stacked_services/CHANGELOG.md index 7a33b7a6..54487047 100644 --- a/packages/stacked_services/CHANGELOG.md +++ b/packages/stacked_services/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.2 + +- Fixed naming changes to be consistent with the function name + ## 0.3.1+2 - When clearing stacking and showing we push instead of replace diff --git a/packages/stacked_services/lib/src/dialog/dialog_service.dart b/packages/stacked_services/lib/src/dialog/dialog_service.dart index a7300a59..851f4645 100644 --- a/packages/stacked_services/lib/src/dialog/dialog_service.dart +++ b/packages/stacked_services/lib/src/dialog/dialog_service.dart @@ -31,8 +31,8 @@ class DialogService { Future showDialog({ String title, String description, - String cancelText, - String confirmText = 'Ok', + String cancelTitle, + String buttonTitle = 'Ok', /// Indicates which [DialogPlatform] to show. /// @@ -45,8 +45,8 @@ class DialogService { _showDialog( title: title, description: description, - cancelText: cancelText, - confirmText: confirmText, + cancelTitle: cancelTitle, + buttonTitle: buttonTitle, dialogPlatform: dialogPlatform); } else { var _dialogType = GetPlatform.isAndroid @@ -55,8 +55,8 @@ class DialogService { _showDialog( title: title, description: description, - cancelText: cancelText, - confirmText: confirmText, + cancelTitle: cancelTitle, + buttonTitle: buttonTitle, dialogPlatform: _dialogType); } @@ -66,11 +66,11 @@ class DialogService { Future _showDialog({ String title, String description, - String cancelText, - String confirmText, + String cancelTitle, + String buttonTitle, DialogPlatform dialogPlatform, }) { - var isConfirmationDialog = cancelText != null; + var isConfirmationDialog = cancelTitle != null; return Get.dialog( PlatformDialog( dialogPlatform: dialogPlatform, @@ -80,7 +80,7 @@ class DialogService { if (isConfirmationDialog) PlatformButton( dialogPlatform: dialogPlatform, - text: cancelText, + text: cancelTitle, isCancelButton: true, onPressed: () { if (!_dialogCompleter.isCompleted) @@ -93,7 +93,7 @@ class DialogService { ), PlatformButton( dialogPlatform: dialogPlatform, - text: confirmText, + text: buttonTitle, onPressed: () { if (!_dialogCompleter.isCompleted) completeDialog( @@ -113,8 +113,8 @@ class DialogService { Future showConfirmationDialog({ String title, String description, - String cancelText = 'Cancel', - String confirmText = 'Ok', + String cancelTitle = 'Cancel', + String confirmationTitle = 'Ok', /// Indicates which [DialogPlatform] to show. /// @@ -126,8 +126,8 @@ class DialogService { showDialog( title: title, description: description, - confirmText: confirmText, - cancelText: cancelText, + buttonTitle: confirmationTitle, + cancelTitle: cancelTitle, dialogPlatform: dialogPlatform, ); diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index 1c540813..bd4bac4b 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked_services description: A package that contains some default implementations of services required for a cleaner implementation of the Stacked Architecture. -version: 0.3.1+2 +version: 0.3.2 homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services environment: From 89f2a6e88265aa5044879818c6290d0f283f5618 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Fri, 22 May 2020 10:30:44 +0200 Subject: [PATCH 21/41] Makes use of Get.offAllNamed function for clearStackAndShow function --- packages/stacked_services/CHANGELOG.md | 4 ++++ packages/stacked_services/lib/src/navigation_service.dart | 8 +------- packages/stacked_services/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/stacked_services/CHANGELOG.md b/packages/stacked_services/CHANGELOG.md index 54487047..635273a5 100644 --- a/packages/stacked_services/CHANGELOG.md +++ b/packages/stacked_services/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.2+! + +- Makes use of offAllNamed from get for clear backstack functionality + ## 0.3.2 - Fixed naming changes to be consistent with the function name diff --git a/packages/stacked_services/lib/src/navigation_service.dart b/packages/stacked_services/lib/src/navigation_service.dart index 21129774..4c2503a4 100644 --- a/packages/stacked_services/lib/src/navigation_service.dart +++ b/packages/stacked_services/lib/src/navigation_service.dart @@ -147,9 +147,7 @@ class NavigationService { /// Clears the entire back stack and shows [routeName] Future clearStackAndShow(String routeName, {dynamic arguments}) { - _clearBackstackCompletely(); - - return navigateTo(routeName, arguments: arguments); + return Get.offAllNamed(routeName); } /// Pops the navigation stack until there's 1 view left then pushes [routeName] onto the stack @@ -173,10 +171,6 @@ class NavigationService { predicate: predicate, arguments: arguments, id: id); } - void _clearBackstackCompletely() { - navigatorKey.currentState.popUntil((Route route) => false); - } - void _clearBackstackTillFirst() { navigatorKey.currentState.popUntil((Route route) => route.isFirst); } diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index bd4bac4b..fb047794 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked_services description: A package that contains some default implementations of services required for a cleaner implementation of the Stacked Architecture. -version: 0.3.2 +version: 0.3.2+1 homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services environment: From 4856ad99ece9d074b1175f17cef432db71220303 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Sun, 24 May 2020 15:19:44 +0200 Subject: [PATCH 22/41] [stacked]-Added MultipleStreamViewModel to readme --- packages/stacked/README.md | 47 ++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/stacked/README.md b/packages/stacked/README.md index 421e621b..cbaf67d2 100644 --- a/packages/stacked/README.md +++ b/packages/stacked/README.md @@ -12,10 +12,6 @@ The architecture is very simple. It consists of 3 major pieces, everything else - **ViewModel**: Manages the state of the View, business logic and any other logic as required from user interaction. It does this by making use of the services - **Services**: A wrapper of a single functionality / feature set. This is commonly used to wrap things like showing a dialog, wrapping database functionality, integrating an api, etc. ---- - -- **Managers(Suggestion)**: A service that requires other services. This piece serves no particular part in the architecture except for indicating that it has depdendencies on other service. It has no functional part in the architecture, It's main purpose is to distinguish between services that depend on other services and ones that don't. It's not a hard rule to follow but will allow for more code separation. - Lets go over some of those principles to follow during development. - Views should never MAKE USE of a service directly. @@ -676,6 +672,49 @@ class MultipleFuturesExampleView extends StatelessWidget { ``` +### MultipleStreamViewModel + +Similarly to the `StreamViewModel` we also have a `MultipleStreamViewModel` which allows you to provide multiple streams through a String key -> Stream paring. Any of the values from these streams will be stored in the data[key] and the same goes for the errors. Each stream value emitted will call notifyListeners to update the UI. `MultipleStreamViewModel` requires the `streamsMap` to be overridden. + +```dart +const String _NumbersStreamKey = 'numbers-stream'; +const String _StringStreamKey = 'string-stream'; + +class MultipleStreamsExampleViewModel extends MultipleStreamViewModel { + int numbersStreamDelay = 500; + int stringStreamDelay = 2000; + + @override + Map get streamsMap => { + _NumbersStreamKey: StreamData(numbersStream(numbersStreamDelay)), + _StringStreamKey: StreamData(stringStream(stringStreamDelay)), + }; + + Stream numbersStream([int delay = 500]) async* { + var random = Random(); + while (true) { + await Future.delayed(Duration(milliseconds: delay)); + yield random.nextInt(999); + } + } + + Stream stringStream([int delay = 2000]) async* { + var random = Random(); + while (true) { + await Future.delayed(Duration(milliseconds: delay)); + var randomLength = random.nextInt(50); + var randomString = ''; + for (var i = 0; i < randomLength; i++) { + randomString += String.fromCharCode(random.nextInt(50)); + } + yield randomString; + } + } +} +``` + +Similarly to the single stream model. When your stream has changed you should call `notifySourceChanged` to let the ViewModel know that it should stop listening to the old stream and subscribe to the new one. If you want to check if the stream had an error you can use the `hasError` function with the key for the stream, you can also get the error using `getError` with the key for the Stream. + ## Migrating from provider_architecture to Stacked Lets start with a statement to ease your migration panic 😅 stacked is the exact same code from provider_architecture with naming changes and removal of some old deprecated properties. If you don't believe me, open the repo's side by side and look at the lib folders. Well, up till yesterday (22 April 2020) I guess, when I updated the BaseViewModel. I wanted to do this to show that stacked is production ready from the go. It's a new package but it's been used by all of you and the FilledStacks development team for months in the form of provider_architecture. With that out of the way lets start the migrate. From 2ee7870b6fb663ea8a707152b040ede05ca98cb0 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Sun, 24 May 2020 15:19:59 +0200 Subject: [PATCH 23/41] Removed the stacked package's readme contents from the main readme --- README.md | 647 +----------------------------------------------------- 1 file changed, 2 insertions(+), 645 deletions(-) diff --git a/README.md b/README.md index 9b95d047..d39e432f 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ That's quite a bit of "rules" but they help during production. Trust me. Stacked provides you with classes and functionalities to make it easy to implement that base architecture that this package is built for. There are additional things that you can add to your application that will make the user of this architecture much more pleasant. This will be discussed in full on the architecture series that will come out soon. Everything from navigation, dependency injection, service location, error handling, etc. -## Additional Packages +## Packages In the efforts of providing as much value with the stacked package as possible the repo contains all of the other packages that extends the stacked functionality further and implements some of the base functionalities for you. It also contains third party extensions that can be used with stacked. @@ -41,650 +41,7 @@ In the efforts of providing as much value with the stacked package as possible t | [stacked_services](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services) | [![pub package](https://img.shields.io/pub/v/stacked_services.svg)](https://pub.dev/packages/stacked_services) | | [stacked_hooks](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_hooks) | [![pub package](https://img.shields.io/pub/v/stacked_hooks.svg)](https://pub.dev/packages/stacked_hooks) | -## ViewModelBuilder - -The `ViewModelBuilder` was first built in the [Provider Architecture Tutorial](https://youtu.be/kDEflMYTFlk) where it was titled BaseView. The `ViewModelBuilder` is used to create the "binding" between a ViewModel and the View. There is no two-way binding in this architecture, which is why I don't want to say it's an Mvvm implementation and why we have instead given it our own name. The `ViewModelBuilder` wraps up all the `ChangeNotifierProvider` code which allows us to trigger a rebuild of a widget when calling `notifyListeners` within the ViewModel. - -A ViewModel is simply a dart class that extends `ChangeNotifier`. The `ViewModelBuilder` has 2 constructors, one that's reactive and one that's not. The tutorial mentioned above emulates the default implementation which has been put into the `.reactive` named constructor. The `.nonReactive` constructor is for UI that does not require the builder to fire when notifyListeners is called in the ViewModel. The nonReactive construction was born in [this tutorial](https://youtu.be/HUSqk0OrR7I?t=224) where we wanted to reduce the boiler plate when the same data has to go to multiple widgets using the same ViewModel. This is very prominent when using the responsive_builder package. - -### Reactive - -This is the default implementation of "binding" your view to your ViewModel. - -```dart - -// View -class HomeView extends StatelessWidget { - @override - Widget build(BuildContext context) { - // Using the reactive constructor gives you the traditional viewmodel - // binding which will excute the builder again when notifyListeners is called. - return ViewModelBuilder.reactive( - viewModelBuilder: () => HomeViewModel(), - onModelReady: (model) => model.initialise(), - builder: (context, model, child) => Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () { - model.updateTitle(); - }, - ), - body: Center( - child: Text(model.title), - ), - ), - ); - } -} - -// ViewModel -class HomeViewModel extends ChangeNotifier { - String title = 'default'; - - void initialise() { - title = 'initialised'; - notifyListeners(); - } - - int counter = 0; - void updateTitle() { - counter++; - title = '$counter'; - notifyListeners(); - } -} - -``` - -When `notifyListeners` is called in the ViewModel the builder is triggered allowing you to rebuild your UI with the new updated ViewModel state. The process here is you update your data then call `notifyListeners` and rebuild your UI. - -### Non Reactive - -The `.nonReactive` constructor is best used for providing your ViewModel to multiple child widgets that will make use of it. It was created to make it easier to build and provide the same ViewModel to multiple UI's. It was born out of the [Responsive UI architecture](https://youtu.be/HUSqk0OrR7I) where we would have to provide the same ViewModel to all the different responsive layouts. Here's a simple example. - -```dart -// Viewmodel in the above code - -// View -class HomeViewMultipleWidgets extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ViewModelBuilder.nonReactive( - viewModelBuilder: () => HomeViewModel(), - onModelReady: (model) => model.initialise(), - builder: (context, model, _) => Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () { - model.updateTitle(); - }, - ), - body: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [TitleSection(), DescriptionSection()], - ), - ), - ); - } -} - -class TitleSection extends ViewModelWidget { - @override - Widget build(BuildContext context, HomeViewModel model) { - return Row( - children: [ - Text( - 'Title', - style: TextStyle(fontSize: 20), - ), - Container( - child: Text(model.title), - ), - ], - ); - } -} - -class DescriptionSection extends ViewModelWidget { - @override - Widget build(BuildContext context, HomeViewModel model) { - return Row( - children: [ - Text( - 'Description', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700), - ), - Container( - child: Text(model.title), - ), - ], - ); - } -} -``` - -So what we're doing here is providing the ViewModel to the children of the builder function. The builder function itself won't retrigger when `notifyListeners` is called. Instead we will extend from `ViewModelWidget` in the widgets that we want to rebuild from the ViewModel. This allows us to easily access the ViewModel in multiple widgets without a lot of repeat boilerplate code. We already extend from a `StatelessWidget` so we can change that to `ViewModelWidget`. Then we simply add the ViewModel as a parameter to the build function. This is the same as calling `Provider.of` in every widget we want to rebuild. - -### ViewModelBuilderWidget - -If you want to make use of the `ViewModelBuilder` directly as a widget is can be extended as well using the `ViewModelBuilderWidget`. This will give you the same properties to override as the ones you can pass into the named constructors. There are 2 required overrides, the same as the 2 required parameters for the constructors. The difference with this is that your code will look like a normal widget so it fits into the code base. You can also override and implement `onModelReady` and `staticChildBuilder`. - -```dart - -class BuilderWidgetExampleView extends ViewModelBuilderWidget { - - @override - bool get reactive => false; - - @override - bool get createNewModelOnInsert => false; - - @override - bool get disposeViewModel => true; - - @override - Widget builder( - BuildContext context, - HomeViewModel model, - Widget child, - ) { - return Scaffold( - body: Center( - child: Text(model.title), - ), - floatingActionButton: FloatingActionButton( - onPressed: () => model.updateTitle(), - ), - ); - } - - @override - HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel(); -} -``` - -This is to help with removing some boilerplate code. - -### Disable ViewModel Dispose - -An example of how to disable the dispose for a viewmodel. - -```dart -// View -class HomeView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - viewModelBuilder: () => HomeViewModel(), - onModelReady: (model) => model.initialise(), - // When the disposeViewModel is set to false the viewmodel will - // not be disposed during the normal life cycle of a widget. - disposeViewModel: false, - builder: (context, model, child) => Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () { - model.updateTitle(); - }, - ), - body: Center( - child: Text(model.title), - ), - ), - ); - } -} - -``` - -Note that the `ViewModelBuilder` constructor is called with parameter `disposeViewModel: false`. This enables us to pass an existing instance of a viewmodel. - -## ViewModel Widget - -The `ViewModelWidget` is an implementation of a widget class that returns a value provided by Provider as a parameter in the build function of the widget. Lets say for instance you have a data model you want to use in multiple widgets. We can use the `Provider.value` call to supply that value, then inside the multiple widgets we inherit from the `ViewModelWidget` and make use of the data directly from the build method. - -```dart - -// View -class HomeView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Scaffold( - body: Provider.value( - value: Human(name: 'Dane', surname: 'Mackier'), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [FullNameWidget(), DuplicateNameWidget()], - ), - ), - ); - } -} - -// Model -class Human { - final String name; - final String surname; - - Human({this.name, this.surname}); -} - -// consuming widget 1 -class FullNameWidget extends ViewModelWidget { - @override - Widget build(BuildContext context, Human model) { - return Row( - children: [ - Container( - child: Text( - model.name, - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), - ), - ), - SizedBox( - width: 50, - ), - Container( - child: Text( - model.surname, - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), - ), - ), - ], - ); - } -} - -// consuming widget 2 -class DuplicateNameWidget extends ViewModelWidget { - @override - Widget build(BuildContext context, Human model) { - return Row( - children: [ - Container( - child: Text(model.name), - ), - SizedBox( - width: 50, - ), - Container( - child: Text(model.name), - ), - ], - ); - } -} -``` - -### Non reactive ViewModelWidget - -Sometimes you want a widget to have access to the ViewModel but you don't want it to rebuild when notifyListeners is called. In this case you can set the reactive value to false for the super constructor of the `ViewModelWidget`. This is commonly used in widgets that don't make use of the models state and only it's functionality. - -```dart -class UpdateTitleButton extends ViewModelWidget { - UpdateTitleButton({ - Key key, - }) : super(key: key, reactive: false); - - @override - Widget build(BuildContext context, model) { - return FloatingActionButton( - onPressed: () { - model.updateTitle(); - }, - ); - } -} -``` - -## BaseViewModel functionality - -This is a `ChangeNotifier` with busy state indication functionality. This allows you to set a busy state based on an object passed it. This will most likely be the properties on the extended ViewModel. It came from the need to have busy states for multiple values in the same ViewModels without relying on implicit state values. It also contains a helper function to indicate busy while a future is executing. This way we avoid having to call setBusy before and after every Future call. - -To use the `BaseViewModel` you can extend it and make use of the busy functionality as follows. - -```dart -class WidgetOneViewModel extends BaseViewModel { - - Human _currentHuman; - Human get currentHuman => _currentHuman; - - void setBusyOnProperty() { - setBusyForObject(_currentHuman, true); - // Fetch updated human data - setBusyForObject(_currentHuman, false); - } - - void setModelBusy() { - setBusy(true); - // Do things here - setBusy(false); - } - - Future longUpdateStuff() async { - // Sets busy to true before starting future and sets it to false after executing - // You can also pass in an object as the busy object. Otherwise it'll use the model - var result = await runBusyFuture(updateStuff()); - } - - Future updateStuff() { - return Future.delayed(const Duration(seconds: 3)); - } -} -``` - -This makes it convenient to use in the UI in a more readable manner. - -```dart -class WidgetOne extends StatelessWidget { - const WidgetOne({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - viewModelBuilder: () => WidgetOneViewModel(), - builder: (context, model, child) => GestureDetector( - onTap: () => model.longUpdateStuff(), - child: Container( - width: 100, - height: 100, - // Use isBusy to check if the model is set to busy - color: model.isBusy ? Colors.green : Colors.red, - alignment: Alignment.center, - // A bit silly to pass the same property back into the viewmodel - // but here it makes sense - child: model.busy(model.currentHuman) - ? Center( - child: CircularProgressIndicator(), - ) - : Container(/* Human Details styling */) - ), - ), - ); - } -} - -``` - -All the major functionality for the BaseViewModel is shown above - -## Reactivity - -One thing that was common a scenario with the first implementation of this architecture that was clearly lacking is reacting to values changed by different ViewModels. I don't have the exact implementation that I would hope for but without reflection some things will have to be a bit more verbose. The stacked architecture makes provision for ViewModels to react to changes to values in a service by making use of RxValue from the [Observable-Ish](https://pub.dev/packages/observable_ish) package. - -### Reactive Service Mixin - -In the stacked library we have a `ReactiveServiceMixin` which can be used to register values to "react" to. When any of these values change the listeners registered with this service will be notified to update their UI. This is definitely not the most effecient way but I have tested this with 1000 widgets with it's own viewmodel all updating on the screen and it works fine. If you follow general good code implementations and layout structuring you will have no problem keeping your app at 60fps no matter the size. - -There are three things you need to make a service reactive. - -1. Use the `ReactiveServiceMixin` with the service you want to make reactive -2. Wrap your values in an RxValue. The value provided by Observable-ish -3. Register your reactive values by calling `listenToReactiveValues`. A function provided by the mixin. - -Below is some source code for the non theory coders out there like myself. - -```dart -class InformationService with ReactiveServiceMixin { //1 - InformationService() { - //3 - listenToReactiveValues([_postCount]); - } - - //2 - RxValue _postCount = RxValue(initial: 0); - int get postCount => _postCount.value; - - void updatePostCount() { - _postCount.value++; - } - - void resetCount() { - _postCount.value = 0; - } -} -``` - -Easy peasy. This service can now be listened too when any of the properties passed into the `listenToReactiveValues` is changed. So how do listen to these values? I'm glad you asked. Lets move onto the `ReactiveViewModel`. - -### Reactive View Model - -This ViewModel extends the `BaseViewModel` and adds an additional function that allows you to listen to services that are being used in the model. There are two thing you have to do to make a ViewModel react to changes in a service. - -1. Extend from `ReactiveViewModel`. -2. Implement reactiveServices getter that return a list of reactive services. - -```dart -class WidgetOneViewModel extends ReactiveViewModel { - // You can use get_it service locator or pass it in through the constructor - final InformationService _informationService = locator(); - - @override - List get reactiveServices => [_informationService]; -} -``` - -That's it. To see a full example take a look at the example in the git repo. - -### StreamViewModel - -This `ViewModel` extends the `BaseViewModel` and provides functionality to easily listen and react to stream data. It allows you to supply a `Stream` of type `T` which it will subscribe to, manage subscription (dispose when done) and give you callbacks where you can modify / manipulate the data. It will automatically rebuild the `ViewModel` as new stream values come in. It has 1 required override which is the stream getter and 4 optional overrides. - -- **stream**: Returns the `Stream` you would like to listen to -- **onData**: Called after the view has rebuilt and provides you with the data to use -- **onCancel**: Called after the stream has been disposed -- **onSubscribed**: Called when the stream has been subscribed to -- **onError**: Called when an error is sent over the stream - -```dart -// ViewModel -class StreamCounterViewModel extends StreamViewModel { - - String get title => 'This is the time since epoch in seconds \n $data'; - - @override - Stream get stream => locator().epochUpdatesNumbers(); -} - -// View -class StreamCounterView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - builder: (context, model, child) => Scaffold( - body: Center( - child: Text(model.title), - ), - ), - viewModelBuilder: () => StreamCounterViewModel(), - ); - } -} - -// Service (registered using injectable, NOT REQUIRED) -@lazySingleton -class EpochService { - Stream epochUpdatesNumbers() async* { - while (true) { - await Future.delayed(const Duration(seconds: 2)); - yield DateTime.now().millisecondsSinceEpoch; - } - } -} -``` - -The code above will listen to a stream and provide you the data to rebuild with. You can create a `ViewModel` that listens to a stream with two lines of code. - -```dart -class StreamCounterViewModel extends StreamViewModel { - @override - Stream get stream => locator().epochUpdatesNumbers(); -} -``` - -Besides having the onError function you can override the `ViewModel` will also set the hasError property to true for easier checking on the view side. The `onError` callback can be used for running additional actions on failure and the `hasError` property should be used when you want to show error specific UI. - -### FutureViewModel - -This `ViewModel` extends the `BaseViewModel` to provide functionality to easily listen to a Future that fetches data. This requirement came off a Details view that has to fetch additional data to show to the user after selecting an item. When you extend the `FutureViewModel` you can provide a type which will then require you to override the future getter where you can set the future you want to run. - -The future will run after the model has been created automatically. - -```dart -class FutureExampleViewModel extends FutureViewModel { - @override - Future get future => getDataFromServer(); - - Future getDataFromServer() async { - await Future.delayed(const Duration(seconds: 3)); - return 'This is fetched from everywhere'; - } -} -``` - -This will automatically set the view's isBusy property and will indicate false when it's complete. It also exposes have a `dataReady` property that can be used. This will indicate true when the data is available. The `ViewModel` can be used in a view as follows. - -```dart -class FutureExampleView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - builder: (context, model, child) => Scaffold( - body: Center( - // model will indicate busy until the future is fetched - child: model.isBusy ? CircularProgressIndicator() : Text(model.data), - ), - ), - viewModelBuilder: () => FutureExampleViewModel(), - ); - } -} -``` - -The `FutureViewModel` will also catch an error and indicate that it has received an error through the `hasError` property. You can also override the onError function if you want to receive that error and perform a specific action at that point. - -```dart -class FutureExampleViewModel extends FutureViewModel { - @override - Future get future => getDataFromServer(); - - Future getDataFromServer() async { - await Future.delayed(const Duration(seconds: 3)); - throw Exception('This is an error'); - } - - @override - void onError(error) { - } -} -``` - -The hasError property can be used in the view the same way as the isBusy property. - -```dart -class FutureExampleView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - builder: (context, model, child) => Scaffold( - body: model.hasError - ? Container( - color: Colors.red, - alignment: Alignment.center, - child: Text( - 'An error has occered while running the future', - style: TextStyle(color: Colors.white), - ), - ) - : Center( - child: model.isBusy - ? CircularProgressIndicator() - : Text(model.data), - ), - ), - viewModelBuilder: () => FutureExampleViewModel(), - ); - } -} -``` - -### MultipleFutureViewModel - -In addition to being able to run a Future you also make a view react to data returned from multiple futures. It requires you to provide a map of type string along with a Function that returns a Future that will be executed after the `ViewModel` has been constructed. See below for an example of using a `MultipleFutureViewModel`. - -```dart -import 'package:stacked/stacked.dart'; - -const String _NumberDelayFuture = 'delayedNumber'; -const String _StringDelayFuture = 'delayedString'; - -class MultipleFuturesExampleViewModel extends MultipleFutureViewModel { - int get fetchedNumber => dataMap[_NumberDelayFuture]; - String get fetchedString => dataMap[_StringDelayFuture]; - - bool get fetchingNumber => busy(_NumberDelayFuture); - bool get fetchingString => busy(_StringDelayFuture); - - @override - Map get futuresMap => { - _NumberDelayFuture: getNumberAfterDelay, - _StringDelayFuture: getStringAfterDelay, - }; - - Future getNumberAfterDelay() async { - await Future.delayed(Duration(seconds: 2)); - return 3; - } - - Future getStringAfterDelay() async { - await Future.delayed(Duration(seconds: 3)); - return 'String data'; - } -} -``` - -The data for the future will be in the `dataMap` when the future is complete. Each future will individually be set to busy using the key for the future passed in. With these functionalities you'll be able to show busy indicator for the UI that depends on the future's data while it's being fetched. There's also a `hasError` function which will indicate if the Future for a specific key has thrown an error. - -```dart -class MultipleFuturesExampleView extends StatelessWidget { - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - builder: (context, model, child) => Scaffold( - body: Center( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 50, - height: 50, - alignment: Alignment.center, - color: Colors.yellow, - // Show busy for number future until the data is back or has failed - child: model.fetchingNumber - ? CircularProgressIndicator() - : Text(model.fetchedNumber.toString()), - ), - SizedBox( - width: 20, - ), - Container( - width: 50, - height: 50, - alignment: Alignment.center, - color: Colors.red, - // Show busy for string future until the data is back or has failed - child: model.fetchingString - ? CircularProgressIndicator() - : Text(model.fetchedString), - ), - ], - ), - ), - ), - viewModelBuilder: () => MultipleFuturesExampleViewModel()); - } -} - -``` +Each package folder contains instructions on how to use the package so please look at the README per package for detailed examples. ## Migrating from provider_architecture to Stacked From c0f41b0abf21d50aca3bc87b1434a97f949cc9c4 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Sun, 24 May 2020 15:21:42 +0200 Subject: [PATCH 24/41] Bumped version --- packages/stacked/CHANGELOG.md | 4 ++++ packages/stacked/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index 7268fc88..b6d18781 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.4+1 + +- Added `MultipleStreamViewModel` example into the readme + ## 1.5.4 - Made specialty viewmodels reactive as well so you can supply reactive services as an override diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index 5322d380..9a4173b5 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.5.4 +version: 1.5.4+1 homepage: https://github.com/FilledStacks/stacked environment: From 590a9cfbd2545ed23e84629ddd8987cf7ee5dbc9 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Mon, 25 May 2020 15:56:41 +0200 Subject: [PATCH 25/41] Sets busy to false when exception is thrown in busy future --- packages/stacked/CHANGELOG.md | 4 ++++ .../stacked/lib/src/base_view_models.dart | 5 ++++- packages/stacked/pubspec.yaml | 2 +- packages/stacked/test/baseviewmodel_test.dart | 22 ++++++++++++++++--- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index b6d18781..de35bdcb 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.5 + +- Added fix to set busy back to false when the future throws an exception + ## 1.5.4+1 - Added `MultipleStreamViewModel` example into the readme diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index 2f06cba7..b5f1414a 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -30,7 +30,10 @@ class BaseViewModel extends ChangeNotifier { /// If a busyKey is su Future runBusyFuture(Future busyFuture, {Object busyObject}) async { _setBusyForModelOrObject(true, busyObject: busyObject); - var value = await busyFuture; + var value = await busyFuture.catchError((error) { + // TODO: Should probably store the error here and indicate we have an error + return null; + }); _setBusyForModelOrObject(false, busyObject: busyObject); return value; } diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index 9a4173b5..788a8476 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.5.4+1 +version: 1.5.5 homepage: https://github.com/FilledStacks/stacked environment: diff --git a/packages/stacked/test/baseviewmodel_test.dart b/packages/stacked/test/baseviewmodel_test.dart index 47f5fd00..6029f468 100644 --- a/packages/stacked/test/baseviewmodel_test.dart +++ b/packages/stacked/test/baseviewmodel_test.dart @@ -2,12 +2,19 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stacked/stacked.dart'; class TestViewModel extends BaseViewModel { - Future runFuture([String busyKey]) { + Future runFuture({String busyKey, bool fail = false}) { return runBusyFuture( - Future.delayed(Duration(milliseconds: 50)), + _futureToRun(fail), busyObject: busyKey, ); } + + Future _futureToRun(bool fail) async { + await Future.delayed(Duration(milliseconds: 50)); + if (fail) { + throw Exception('Broken Future'); + } + } } void main() { @@ -49,9 +56,18 @@ void main() { () { var busyObjectKey = 'busyObjectKey'; var viewModel = TestViewModel(); - viewModel.runFuture(busyObjectKey); + viewModel.runFuture(busyKey: busyObjectKey); expect(viewModel.busy(busyObjectKey), true); }); + + test( + 'When busyFuture is run with busyObject should report NOT busy when error is thrown', + () async { + var busyObjectKey = 'busyObjectKey'; + var viewModel = TestViewModel(); + await viewModel.runFuture(busyKey: busyObjectKey, fail: true); + expect(viewModel.busy(busyObjectKey), false); + }); }); }); } From 211e8301c45289ff73a0c81290937ca09d71abae Mon Sep 17 00:00:00 2001 From: Parthasarathy Gudivada Date: Tue, 26 May 2020 04:23:37 -0400 Subject: [PATCH 26/41] initial commit to address the TODO on open/closed for invoking additional actions on specialized view models --- .../stacked/lib/src/base_view_models.dart | 32 ++++++++++++++++--- .../stacked/lib/src/view_model_builder.dart | 19 ++--------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index b5f1414a..6c5e0198 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -139,7 +139,8 @@ class _MultiDataSourceViewModel extends DynamicSourceViewModel { } /// Provides functionality for a ViewModel that's sole purpose it is to fetch data using a [Future] -abstract class FutureViewModel extends _SingleDataSourceViewModel { +abstract class FutureViewModel extends _SingleDataSourceViewModel + implements IAdditionalSetup { /// The future that fetches the data and sets the view to busy @Deprecated('Use the futureToRun function') Future get future => null; @@ -178,10 +179,15 @@ abstract class FutureViewModel extends _SingleDataSourceViewModel { /// Called after the data has been set void onData(T data) {} + + doSetup() { + runFuture(); + } } /// Provides functionality for a ViewModel to run and fetch data using multiple future -abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel { +abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel + implements IAdditionalSetup { Map get futuresMap; Completer _futuresCompleter; @@ -241,10 +247,15 @@ abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel { void onError({String key, error}) {} void onData(String key) {} + + doSetup() { + runFutures(); + } } /// Provides functionality for a ViewModel to run and fetch data using multiple streams -abstract class MultipleStreamViewModel extends _MultiDataSourceViewModel { +abstract class MultipleStreamViewModel extends _MultiDataSourceViewModel + implements IAdditionalSetup { // Every MultipleStreamViewModel must override streamDataMap // StreamData requires a stream, but lifecycle events are optional // if a lifecyle event isn't defined we use the default ones here @@ -349,10 +360,14 @@ abstract class MultipleStreamViewModel extends _MultiDataSourceViewModel { _streamsSubscriptions.clear(); } } + + doSetup() { + initialise(); + } } abstract class StreamViewModel extends _SingleDataSourceViewModel - implements DynamicSourceViewModel { + implements DynamicSourceViewModel, IAdditionalSetup { /// Stream to listen to Stream get stream; @@ -428,6 +443,10 @@ abstract class StreamViewModel extends _SingleDataSourceViewModel super.dispose(); } + + doSetup() { + initialise(); + } } class StreamData extends _SingleDataSourceViewModel { @@ -501,3 +520,8 @@ class StreamData extends _SingleDataSourceViewModel { super.dispose(); } } + +/// Interface: Additional actions that should be implemented by spcialised ViewModels +abstract class IAdditionalSetup { + void doSetup(); +} diff --git a/packages/stacked/lib/src/view_model_builder.dart b/packages/stacked/lib/src/view_model_builder.dart index cd2a7170..1d336299 100644 --- a/packages/stacked/lib/src/view_model_builder.dart +++ b/packages/stacked/lib/src/view_model_builder.dart @@ -93,22 +93,9 @@ class _ViewModelBuilderState void _initialiseSpecialViewModels() { // Add any additional actions here for spcialised ViewModels - // TODO: Provide a closed implemenation of this functionality. Refer to the Open Closed - // principle in the SOLID principles - if (_model is FutureViewModel) { - (_model as FutureViewModel).runFuture(); - } - - if (_model is MultipleFutureViewModel) { - (_model as MultipleFutureViewModel).runFutures(); - } - - if (_model is StreamViewModel) { - (_model as StreamViewModel).initialise(); - } - - if (_model is MultipleStreamViewModel) { - (_model as MultipleStreamViewModel).initialise(); + // Add any additional actions here for spcialised ViewModels + if (_model is IAdditionalSetup) { + (_model as IAdditionalSetup).doSetup(); } } From de742d82c195ab67e270110b0d32e6b252933ac8 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Wed, 27 May 2020 06:56:18 +0200 Subject: [PATCH 27/41] Reverted error handling directly on runBusyFuture function --- packages/stacked/lib/src/base_view_models.dart | 5 +---- packages/stacked/test/baseviewmodel_test.dart | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index b5f1414a..2f06cba7 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -30,10 +30,7 @@ class BaseViewModel extends ChangeNotifier { /// If a busyKey is su Future runBusyFuture(Future busyFuture, {Object busyObject}) async { _setBusyForModelOrObject(true, busyObject: busyObject); - var value = await busyFuture.catchError((error) { - // TODO: Should probably store the error here and indicate we have an error - return null; - }); + var value = await busyFuture; _setBusyForModelOrObject(false, busyObject: busyObject); return value; } diff --git a/packages/stacked/test/baseviewmodel_test.dart b/packages/stacked/test/baseviewmodel_test.dart index 6029f468..db3aacf0 100644 --- a/packages/stacked/test/baseviewmodel_test.dart +++ b/packages/stacked/test/baseviewmodel_test.dart @@ -67,7 +67,9 @@ void main() { var viewModel = TestViewModel(); await viewModel.runFuture(busyKey: busyObjectKey, fail: true); expect(viewModel.busy(busyObjectKey), false); - }); + }, + skip: + 'Error handling has to be moved into the base classes for this to work'); }); }); } From ff4c58f8369d5c33f16c3f1286a3d2c0c7f50cc0 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Wed, 27 May 2020 06:58:27 +0200 Subject: [PATCH 28/41] [stacked] - Bumped version --- packages/stacked/CHANGELOG.md | 4 ++++ packages/stacked/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index de35bdcb..0ea4ddc0 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.5+1 + +- Reverted commit below + ## 1.5.5 - Added fix to set busy back to false when the future throws an exception diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index 788a8476..834ad318 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.5.5 +version: 1.5.5+1 homepage: https://github.com/FilledStacks/stacked environment: From df898555e2419c858ddc286a724e41e826ce1cf3 Mon Sep 17 00:00:00 2001 From: Parthasarathy Gudivada Date: Wed, 27 May 2020 03:54:11 -0400 Subject: [PATCH 29/41] updated gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..63bdea5e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.project +org.eclipse.buildship.core.prefs +.classpath From 6c912e197d8b01aabfec0dce328322f3a7f92d51 Mon Sep 17 00:00:00 2001 From: Parthasarathy Gudivada Date: Wed, 27 May 2020 05:47:16 -0400 Subject: [PATCH 30/41] Updated naming of the interface name based on the feedback --- packages/stacked/lib/src/base_view_models.dart | 14 +++----------- packages/stacked/lib/src/view_model_builder.dart | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index dc2eb1a7..282e509a 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -177,7 +177,7 @@ abstract class FutureViewModel extends _SingleDataSourceViewModel /// Called after the data has been set void onData(T data) {} - doSetup() { + initialise() { runFuture(); } } @@ -245,7 +245,7 @@ abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel void onData(String key) {} - doSetup() { + initialise() { runFutures(); } } @@ -357,10 +357,6 @@ abstract class MultipleStreamViewModel extends _MultiDataSourceViewModel _streamsSubscriptions.clear(); } } - - doSetup() { - initialise(); - } } abstract class StreamViewModel extends _SingleDataSourceViewModel @@ -440,10 +436,6 @@ abstract class StreamViewModel extends _SingleDataSourceViewModel super.dispose(); } - - doSetup() { - initialise(); - } } class StreamData extends _SingleDataSourceViewModel { @@ -520,5 +512,5 @@ class StreamData extends _SingleDataSourceViewModel { /// Interface: Additional actions that should be implemented by spcialised ViewModels abstract class IAdditionalSetup { - void doSetup(); + void initialise() ; } diff --git a/packages/stacked/lib/src/view_model_builder.dart b/packages/stacked/lib/src/view_model_builder.dart index 1d336299..e7ba12b2 100644 --- a/packages/stacked/lib/src/view_model_builder.dart +++ b/packages/stacked/lib/src/view_model_builder.dart @@ -95,7 +95,7 @@ class _ViewModelBuilderState // Add any additional actions here for spcialised ViewModels // Add any additional actions here for spcialised ViewModels if (_model is IAdditionalSetup) { - (_model as IAdditionalSetup).doSetup(); + (_model as IAdditionalSetup).initialise(); } } From a11ad44b226cefa092e640a3f116d3bddc6d45e4 Mon Sep 17 00:00:00 2001 From: Parthasarathy Gudivada Date: Sat, 30 May 2020 03:54:01 -0400 Subject: [PATCH 31/41] Implemented changes based on the feedback --- .../stacked/lib/src/base_view_models.dart | 22 ++++-------- .../stacked/lib/src/view_model_builder.dart | 5 ++- .../stacked/test/futureviewmodel_test.dart | 36 +++++++++---------- 3 files changed, 27 insertions(+), 36 deletions(-) diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index 282e509a..797d0ad7 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -137,7 +137,7 @@ class _MultiDataSourceViewModel extends DynamicSourceViewModel { /// Provides functionality for a ViewModel that's sole purpose it is to fetch data using a [Future] abstract class FutureViewModel extends _SingleDataSourceViewModel - implements IAdditionalSetup { + implements Initialisable { /// The future that fetches the data and sets the view to busy @Deprecated('Use the futureToRun function') Future get future => null; @@ -148,7 +148,7 @@ abstract class FutureViewModel extends _SingleDataSourceViewModel Future futureToRun(); - Future runFuture() async { + Future initialise() async { _hasError = false; _error = null; // We set busy manually as well because when notify listeners is called to clear error messages the @@ -176,15 +176,11 @@ abstract class FutureViewModel extends _SingleDataSourceViewModel /// Called after the data has been set void onData(T data) {} - - initialise() { - runFuture(); - } } /// Provides functionality for a ViewModel to run and fetch data using multiple future abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel - implements IAdditionalSetup { + implements Initialisable { Map get futuresMap; Completer _futuresCompleter; @@ -204,7 +200,7 @@ abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel _futuresCompleted = 0; } - Future runFutures() { + Future initialise() { _futuresCompleter = Completer(); _initialiseData(); // We set busy manually as well because when notify listeners is called to clear error messages the @@ -244,15 +240,11 @@ abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel void onError({String key, error}) {} void onData(String key) {} - - initialise() { - runFutures(); - } } /// Provides functionality for a ViewModel to run and fetch data using multiple streams abstract class MultipleStreamViewModel extends _MultiDataSourceViewModel - implements IAdditionalSetup { + implements Initialisable { // Every MultipleStreamViewModel must override streamDataMap // StreamData requires a stream, but lifecycle events are optional // if a lifecyle event isn't defined we use the default ones here @@ -360,7 +352,7 @@ abstract class MultipleStreamViewModel extends _MultiDataSourceViewModel } abstract class StreamViewModel extends _SingleDataSourceViewModel - implements DynamicSourceViewModel, IAdditionalSetup { + implements DynamicSourceViewModel, Initialisable { /// Stream to listen to Stream get stream; @@ -511,6 +503,6 @@ class StreamData extends _SingleDataSourceViewModel { } /// Interface: Additional actions that should be implemented by spcialised ViewModels -abstract class IAdditionalSetup { +abstract class Initialisable { void initialise() ; } diff --git a/packages/stacked/lib/src/view_model_builder.dart b/packages/stacked/lib/src/view_model_builder.dart index e7ba12b2..7cb60055 100644 --- a/packages/stacked/lib/src/view_model_builder.dart +++ b/packages/stacked/lib/src/view_model_builder.dart @@ -1,4 +1,3 @@ -import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; import 'base_view_models.dart'; @@ -94,8 +93,8 @@ class _ViewModelBuilderState void _initialiseSpecialViewModels() { // Add any additional actions here for spcialised ViewModels // Add any additional actions here for spcialised ViewModels - if (_model is IAdditionalSetup) { - (_model as IAdditionalSetup).initialise(); + if (_model is Initialisable) { + (_model as Initialisable).initialise(); } } diff --git a/packages/stacked/test/futureviewmodel_test.dart b/packages/stacked/test/futureviewmodel_test.dart index 6fdb2532..e4b61c4d 100644 --- a/packages/stacked/test/futureviewmodel_test.dart +++ b/packages/stacked/test/futureviewmodel_test.dart @@ -57,7 +57,7 @@ void main() { group('FutureViewModel', () { test('When future is complete data should be set and ready', () async { var futureViewModel = TestFutureViewModel(); - await futureViewModel.runFuture(); + await futureViewModel.initialise(); expect(futureViewModel.data, 5); expect(futureViewModel.dataReady, true); }); @@ -65,7 +65,7 @@ void main() { test('When a future fails it should indicate there\'s an error and no data', () async { var futureViewModel = TestFutureViewModel(fail: true); - await futureViewModel.runFuture(); + await futureViewModel.initialise(); expect(futureViewModel.hasError, true); expect(futureViewModel.data, null, reason: 'No data should be set when there\'s a failure.'); @@ -74,42 +74,42 @@ void main() { test('When a future runs it should indicate busy', () async { var futureViewModel = TestFutureViewModel(); - futureViewModel.runFuture(); + futureViewModel.initialise(); expect(futureViewModel.isBusy, true); }); test('When a future fails it should indicate NOT busy', () async { var futureViewModel = TestFutureViewModel(fail: true); - await futureViewModel.runFuture(); + await futureViewModel.initialise(); expect(futureViewModel.isBusy, false); }); test('When a future fails it should set error to exception', () async { var futureViewModel = TestFutureViewModel(fail: true); - await futureViewModel.runFuture(); + await futureViewModel.initialise(); expect(futureViewModel.error.message, _SingleFutureExceptionFailMessage); }); test('When a future fails onData should not be called', () async { var futureViewModel = TestFutureViewModel(fail: true); - await futureViewModel.runFuture(); + await futureViewModel.initialise(); expect(futureViewModel.dataCalled, false); }); test('When a future passes onData should not called', () async { var futureViewModel = TestFutureViewModel(); - await futureViewModel.runFuture(); + await futureViewModel.initialise(); expect(futureViewModel.dataCalled, true); }); group('Dynamic Source Tests', () { test('notifySourceChanged - When called should re-run Future', () async { var futureViewModel = TestFutureViewModel(); - await futureViewModel.runFuture(); + await futureViewModel.initialise(); expect(futureViewModel.data, 5); futureViewModel.numberToReturn = 10; futureViewModel.notifySourceChanged(); - await futureViewModel.runFuture(); + await futureViewModel.initialise(); expect(futureViewModel.data, 10); }); }); @@ -120,7 +120,7 @@ void main() { 'When running multiple futures the associated key should hold the value when complete', () async { var futureViewModel = TestMultipleFutureViewModel(); - await futureViewModel.runFutures(); + await futureViewModel.initialise(); expect(futureViewModel.dataMap[NumberDelayFuture], 5); expect(futureViewModel.dataMap[StringDelayFuture], 'String data'); @@ -130,7 +130,7 @@ void main() { 'When one of multiple futures fail only the failing one should have an error', () async { var futureViewModel = TestMultipleFutureViewModel(failOne: true); - await futureViewModel.runFutures(); + await futureViewModel.initialise(); expect(futureViewModel.hasError(NumberDelayFuture), true); expect(futureViewModel.hasError(StringDelayFuture), false); @@ -140,7 +140,7 @@ void main() { 'When one of multiple futures fail the passed one should have data and failing one not', () async { var futureViewModel = TestMultipleFutureViewModel(failOne: true); - await futureViewModel.runFutures(); + await futureViewModel.initialise(); expect(futureViewModel.dataMap[NumberDelayFuture], null); expect(futureViewModel.dataMap[StringDelayFuture], 'String data'); @@ -149,7 +149,7 @@ void main() { test('When multiple futures run the key should be set to indicate busy', () async { var futureViewModel = TestMultipleFutureViewModel(); - futureViewModel.runFutures(); + futureViewModel.initialise(); expect(futureViewModel.busy(NumberDelayFuture), true); expect(futureViewModel.busy(StringDelayFuture), true); @@ -159,7 +159,7 @@ void main() { 'When multiple futures are complete the key should be set to indicate NOT busy', () async { var futureViewModel = TestMultipleFutureViewModel(); - await futureViewModel.runFutures(); + await futureViewModel.initialise(); expect(futureViewModel.busy(NumberDelayFuture), false); expect(futureViewModel.busy(StringDelayFuture), false); @@ -167,7 +167,7 @@ void main() { test('When a future fails busy should be set to false', () async { var futureViewModel = TestMultipleFutureViewModel(failOne: true); - await futureViewModel.runFutures(); + await futureViewModel.initialise(); expect(futureViewModel.busy(NumberDelayFuture), false); expect(futureViewModel.busy(StringDelayFuture), false); @@ -175,7 +175,7 @@ void main() { test('When a future fails should set error for future key', () async { var futureViewModel = TestMultipleFutureViewModel(failOne: true); - await futureViewModel.runFutures(); + await futureViewModel.initialise(); expect(futureViewModel.getError(NumberDelayFuture).message, _NumberDelayExceptionMessage); @@ -186,11 +186,11 @@ void main() { group('Dynamic Source Tests', () { test('notifySourceChanged - When called should re-run Future', () async { var futureViewModel = TestMultipleFutureViewModel(); - await futureViewModel.runFutures(); + await futureViewModel.initialise(); expect(futureViewModel.dataMap[NumberDelayFuture], 5); futureViewModel.numberToReturn = 10; futureViewModel.notifySourceChanged(); - await futureViewModel.runFutures(); + await futureViewModel.initialise(); expect(futureViewModel.dataMap[NumberDelayFuture], 10); }); }); From 2f9ad9d27976a619bd03fd04ac8c54811467f038 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Wed, 3 Jun 2020 05:27:03 +0200 Subject: [PATCH 32/41] [stacked] - Busy automatically set to false for exceptions - runBusyFuture now sets itself to not busy when exception is thrown - runBusyFuture has parameter throwException which will rethrow the caught exception to the caller --- packages/stacked/CHANGELOG.md | 5 ++++ .../stacked/lib/src/base_view_models.dart | 22 ++++++++++------ packages/stacked/pubspec.yaml | 2 +- packages/stacked/test/baseviewmodel_test.dart | 25 +++++++++++++------ 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index 0ea4ddc0..17cd0b81 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.5.6 + +- runBusyFuture now sets itself to not busy when exception is thrown +- runBusyFuture has parameter throwException which will rethrow the caught exception to the caller + ## 1.5.5+1 - Reverted commit below diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index 2f06cba7..51e1ec48 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -27,12 +27,17 @@ class BaseViewModel extends ChangeNotifier { /// Sets the ViewModel to busy, runs the future and then sets it to not busy when complete. /// - /// If a busyKey is su - Future runBusyFuture(Future busyFuture, {Object busyObject}) async { + /// rethrows [Exception] after setting busy to false for object or class + Future runBusyFuture(Future busyFuture, + {Object busyObject, bool throwException = false}) async { _setBusyForModelOrObject(true, busyObject: busyObject); - var value = await busyFuture; - _setBusyForModelOrObject(false, busyObject: busyObject); - return value; + try { + var value = await busyFuture; + return value; + } catch (e) { + _setBusyForModelOrObject(false, busyObject: busyObject); + if (throwException) rethrow; + } } void _setBusyForModelOrObject(bool value, {Object busyObject}) { @@ -155,7 +160,8 @@ abstract class FutureViewModel extends _SingleDataSourceViewModel { setBusy(true); notifyListeners(); - _data = await runBusyFuture(futureToRun()).catchError((error) { + _data = await runBusyFuture(futureToRun(), throwException: true) + .catchError((error) { _hasError = true; _error = error; setBusy(false); @@ -207,8 +213,10 @@ abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel { notifyListeners(); for (var key in futuresMap.keys) { - runBusyFuture(futuresMap[key](), busyObject: key).then((futureData) { + runBusyFuture(futuresMap[key](), busyObject: key, throwException: true) + .then((futureData) { _dataMap[key] = futureData; + setBusyForObject(key, false); notifyListeners(); onData(key); _incrementAndCheckFuturesCompleted(); diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index 834ad318..85dcccae 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.5.5+1 +version: 1.5.6 homepage: https://github.com/FilledStacks/stacked environment: diff --git a/packages/stacked/test/baseviewmodel_test.dart b/packages/stacked/test/baseviewmodel_test.dart index db3aacf0..d0a9f75a 100644 --- a/packages/stacked/test/baseviewmodel_test.dart +++ b/packages/stacked/test/baseviewmodel_test.dart @@ -2,11 +2,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stacked/stacked.dart'; class TestViewModel extends BaseViewModel { - Future runFuture({String busyKey, bool fail = false}) { - return runBusyFuture( - _futureToRun(fail), - busyObject: busyKey, - ); + Future runFuture( + {String busyKey, bool fail = false, bool throwException = false}) { + return runBusyFuture(_futureToRun(fail), + busyObject: busyKey, throwException: throwException); } Future _futureToRun(bool fail) async { @@ -67,9 +66,19 @@ void main() { var viewModel = TestViewModel(); await viewModel.runFuture(busyKey: busyObjectKey, fail: true); expect(viewModel.busy(busyObjectKey), false); - }, - skip: - 'Error handling has to be moved into the base classes for this to work'); + }); + + test( + 'When busyFuture is run with busyObject should throw exception if throwException is set to true', + () async { + var busyObjectKey = 'busyObjectKey'; + var viewModel = TestViewModel(); + + expect( + () async => await viewModel.runFuture( + busyKey: busyObjectKey, fail: true, throwException: true), + throwsException); + }); }); }); } From 36a7556dea2fb940a9473e4c0f8786494b37ec4d Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Wed, 3 Jun 2020 05:32:56 +0200 Subject: [PATCH 33/41] Remove scheduler that's causing a rating drop --- packages/stacked/lib/src/view_model_builder.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/stacked/lib/src/view_model_builder.dart b/packages/stacked/lib/src/view_model_builder.dart index cd2a7170..c2e5e53b 100644 --- a/packages/stacked/lib/src/view_model_builder.dart +++ b/packages/stacked/lib/src/view_model_builder.dart @@ -1,4 +1,3 @@ -import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; import 'base_view_models.dart'; @@ -158,5 +157,5 @@ class _ViewModelBuilderState } } -/// EXPERIMENTAL: Returns the ViewModel provided above this widget in the tree +/// EXPERIMENTAL: Returns the ViewModel provided above this widget in the tree T getParentViewModel(BuildContext context) => Provider.of(context); From 249e053a4f007f0431bb9f1e3e71323141eb1cf6 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Wed, 3 Jun 2020 05:36:19 +0200 Subject: [PATCH 34/41] Fixed silly bug --- packages/stacked/CHANGELOG.md | 4 ++++ packages/stacked/lib/src/base_view_models.dart | 1 + packages/stacked/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index 17cd0b81..e1464019 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.6+1 + +- added back busy to false state when busy future is complete without an error + ## 1.5.6 - runBusyFuture now sets itself to not busy when exception is thrown diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index 51e1ec48..7d65d579 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -33,6 +33,7 @@ class BaseViewModel extends ChangeNotifier { _setBusyForModelOrObject(true, busyObject: busyObject); try { var value = await busyFuture; + _setBusyForModelOrObject(false, busyObject: busyObject); return value; } catch (e) { _setBusyForModelOrObject(false, busyObject: busyObject); diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index 85dcccae..7a1bfebd 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.5.6 +version: 1.5.6+1 homepage: https://github.com/FilledStacks/stacked environment: From 2338e6ed7aee44d750c6f9def3604e7fe7bb3b0e Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Wed, 3 Jun 2020 05:39:01 +0200 Subject: [PATCH 35/41] Added unit test to prevent bug from 1.5.6 --- packages/stacked/test/baseviewmodel_test.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/stacked/test/baseviewmodel_test.dart b/packages/stacked/test/baseviewmodel_test.dart index d0a9f75a..afb9eab2 100644 --- a/packages/stacked/test/baseviewmodel_test.dart +++ b/packages/stacked/test/baseviewmodel_test.dart @@ -79,6 +79,18 @@ void main() { busyKey: busyObjectKey, fail: true, throwException: true), throwsException); }); + + test( + 'When busy future is complete should have called notifyListeners twice, 1 for busy 1 for not busy', + () async { + var called = 0; + var viewModel = TestViewModel(); + viewModel.addListener(() { + ++called; + }); + await viewModel.runFuture(fail: true); + expect(called, 2); + }); }); }); } From a5c7c437f30f611b41b9715c34151758c5b33f07 Mon Sep 17 00:00:00 2001 From: Dane Mackier Date: Wed, 3 Jun 2020 06:10:28 +0200 Subject: [PATCH 36/41] notify listeners exposed to services Fixed busy state and exposed notify listeners to the services to allow for rebuilds --- packages/stacked/CHANGELOG.md | 8 ++++++++ packages/stacked/lib/src/base_view_models.dart | 2 +- .../stacked/lib/src/reactive_service_mixin.dart | 13 ++++++++++--- packages/stacked/pubspec.yaml | 2 +- .../test/reactive_functionality_test.dart | 17 +++++++++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/packages/stacked/CHANGELOG.md b/packages/stacked/CHANGELOG.md index e1464019..6f4351bb 100644 --- a/packages/stacked/CHANGELOG.md +++ b/packages/stacked/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.5.7+1 + +- fixed busy state hash code problem + +## 1.5.7 + +- Exposes notifyListeners to the service for rebuilding ViewModels that are listening + ## 1.5.6+1 - added back busy to false state when busy future is complete without an error diff --git a/packages/stacked/lib/src/base_view_models.dart b/packages/stacked/lib/src/base_view_models.dart index 7d65d579..c795487a 100644 --- a/packages/stacked/lib/src/base_view_models.dart +++ b/packages/stacked/lib/src/base_view_models.dart @@ -43,7 +43,7 @@ class BaseViewModel extends ChangeNotifier { void _setBusyForModelOrObject(bool value, {Object busyObject}) { if (busyObject != null) { - setBusyForObject(busyObject.hashCode, value); + setBusyForObject(busyObject, value); } else { setBusyForObject(this, value); } diff --git a/packages/stacked/lib/src/reactive_service_mixin.dart b/packages/stacked/lib/src/reactive_service_mixin.dart index 4c19b46e..28443cee 100644 --- a/packages/stacked/lib/src/reactive_service_mixin.dart +++ b/packages/stacked/lib/src/reactive_service_mixin.dart @@ -1,28 +1,35 @@ +import 'package:flutter/cupertino.dart'; import 'package:observable_ish/observable_ish.dart'; /// Adds functionality to easily listen to all reactive values in a service mixin ReactiveServiceMixin { List _listeners = List(); + /// List to the values and react when there are any changes void listenToReactiveValues(List reactiveValues) { for (var reactiveValue in reactiveValues) { if (reactiveValue is RxValue) { - reactiveValue.values.listen((value) => _notifyListeners()); + reactiveValue.values.listen((value) => notifyListeners()); } else if (reactiveValue is RxList) { - reactiveValue.onChange.listen((event) => _notifyListeners()); + reactiveValue.onChange.listen((event) => notifyListeners()); } } } + /// Registers a listener with this service void addListener(void Function() listener) { _listeners.add(listener); } + /// Removes a listener from the service void removeListener(void Function() listener) { _listeners.remove(listener); } - void _notifyListeners() { + /// Notifies all the listeners attached to this service + @protected + @visibleForTesting + void notifyListeners() { for (var listener in _listeners) { listener(); } diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml index 7a1bfebd..ed916b22 100644 --- a/packages/stacked/pubspec.yaml +++ b/packages/stacked/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked description: An architecture and widgets for an MVVM inspired architecture in Flutter. It provides common functionalities required to build a large application in a understandable manner. -version: 1.5.6+1 +version: 1.5.7+1 homepage: https://github.com/FilledStacks/stacked environment: diff --git a/packages/stacked/test/reactive_functionality_test.dart b/packages/stacked/test/reactive_functionality_test.dart index 11626121..cdeb3296 100644 --- a/packages/stacked/test/reactive_functionality_test.dart +++ b/packages/stacked/test/reactive_functionality_test.dart @@ -75,6 +75,23 @@ void main() { // expected to happen in the same CPU cycle so this is perfect for a unit test. await Future.delayed(Duration(milliseconds: 10)); + expect(called, true); + }); + test('When notifyListeners is called on a service whould fire the listener', + () async { + var called = false; + var reactiveService = MultipleCounterService(); + + reactiveService.addListener(() { + called = true; + }); + + reactiveService.notifyListeners(); + + // Have to wait for the listener to be called above. In real life the results is not + // expected to happen in the same CPU cycle so this is perfect for a unit test. + await Future.delayed(Duration(milliseconds: 10)); + expect(called, true); }); }); From 46c59c303f16b9e6b894c20daedd2bd506deb4d4 Mon Sep 17 00:00:00 2001 From: Parthasarathy Gudivada Date: Thu, 4 Jun 2020 06:27:29 -0400 Subject: [PATCH 37/41] updated --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4fef768c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.project +org.eclipse.buildship.core.prefs +.classpath +launch.json From 2f8228def4c3301da523acc536f59ab73fd76344 Mon Sep 17 00:00:00 2001 From: Rohit Gupta Date: Thu, 4 Jun 2020 21:13:37 +0530 Subject: [PATCH 38/41] Update widget to use DialogTheme of context --- packages/stacked_services/lib/src/dialog/platform_dialog.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stacked_services/lib/src/dialog/platform_dialog.dart b/packages/stacked_services/lib/src/dialog/platform_dialog.dart index 0057488f..e88735af 100644 --- a/packages/stacked_services/lib/src/dialog/platform_dialog.dart +++ b/packages/stacked_services/lib/src/dialog/platform_dialog.dart @@ -108,8 +108,8 @@ class PlatformDialog extends StatelessWidget { case DialogPlatform.Material: default: // TODO: When custom dialog registrations are implemented it'll be shown here return AlertDialog( - titleTextStyle: Theme.of(context).textTheme.headline6, - contentTextStyle: Theme.of(context).textTheme.bodyText1, + titleTextStyle: Theme.of(context).dialogTheme.titleTextStyle, + contentTextStyle: Theme.of(context).dialogTheme.contentTextStyle, title: Text(title), content: Text(content), actions: actions, From 4f2047f84360625ab94410ef08370a8bc6f718c9 Mon Sep 17 00:00:00 2001 From: Rohit Gupta Date: Thu, 4 Jun 2020 21:15:54 +0530 Subject: [PATCH 39/41] Bump patch version --- packages/stacked_services/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index fb047794..849ab39a 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked_services description: A package that contains some default implementations of services required for a cleaner implementation of the Stacked Architecture. -version: 0.3.2+1 +version: 0.3.3+1 homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services environment: From 5262e10017d30a58b8811aae15d9f6b72c282e33 Mon Sep 17 00:00:00 2001 From: Rohit Gupta Date: Thu, 4 Jun 2020 21:19:46 +0530 Subject: [PATCH 40/41] Update CHANGELOG.md --- packages/stacked_services/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/stacked_services/CHANGELOG.md b/packages/stacked_services/CHANGELOG.md index 635273a5..1f91b520 100644 --- a/packages/stacked_services/CHANGELOG.md +++ b/packages/stacked_services/CHANGELOG.md @@ -1,4 +1,8 @@ -## 0.3.2+! +## 0.3.3 + +- DialogService now uses DialogTheme of the context instead of TextTheme + +## 0.3.2+1 - Makes use of offAllNamed from get for clear backstack functionality From 98aa2eeef752015d0b29a103a29d0acad6833fe4 Mon Sep 17 00:00:00 2001 From: Rohit Gupta Date: Thu, 4 Jun 2020 21:21:07 +0530 Subject: [PATCH 41/41] Fix version info --- packages/stacked_services/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml index 849ab39a..3bca5bc7 100644 --- a/packages/stacked_services/pubspec.yaml +++ b/packages/stacked_services/pubspec.yaml @@ -1,6 +1,6 @@ name: stacked_services description: A package that contains some default implementations of services required for a cleaner implementation of the Stacked Architecture. -version: 0.3.3+1 +version: 0.3.3 homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services environment: