From 1b2f98ebda783a41c41008a25dac505dc6ab88b5 Mon Sep 17 00:00:00 2001 From: Shyam Parmar Date: Fri, 14 May 2021 10:45:26 +0930 Subject: [PATCH] Feature/null safety (#16) * Delete android plugin file. * Package update. * Dart 2.0 migration. * test case update * Add build runner code. * Add generated test in git ignore. * Add generated test in git ignore * Make platform data optional. * Update test cases * Try to fix travis ci -1 * Rename files. * Delete old files. * Bump the version to 1.0.5 * Version bump 2.0.0 * Apply proper casing on App dialog file. * Make os variable required. * Init manUpData with blank state. * Update test cases * Add os param optional and fallback to operating system. Co-authored-by: sparmar --- .gitignore | 1 + .travis.yml | 2 + CHANGELOG.md | 32 ++- README.md | 15 +- .../plugins/GeneratedPluginRegistrant.java | 29 --- lib/manup.dart | 14 +- lib/src/config_storage.dart | 8 +- lib/src/exception.dart | 2 +- ...nup_delegate.dart => man_up_delegate.dart} | 6 +- ...manup_service.dart => man_up_service.dart} | 92 +++---- .../{manup_status.dart => man_up_status.dart} | 0 lib/src/metadata.dart | 36 ++- ..._mixin.dart => man_up_delegate_mixin.dart} | 4 +- ...og_mixin.dart => man_up_dialog_mixin.dart} | 25 +- ...app_dialog.dart => man_up_app_dialog.dart} | 12 +- lib/src/ui/man_up_widget.dart | 82 +++++++ lib/src/ui/manup_widget.dart | 81 ------ pubspec.lock | 133 ++++++++++ pubspec.yaml | 8 +- test/mandatory_update_test.dart | 232 ++++++++++-------- 20 files changed, 499 insertions(+), 315 deletions(-) delete mode 100644 android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java rename lib/src/{manup_delegate.dart => man_up_delegate.dart} (58%) rename lib/src/{manup_service.dart => man_up_service.dart} (58%) rename lib/src/{manup_status.dart => man_up_status.dart} (100%) rename lib/src/mixin/{manup_delegate_mixin.dart => man_up_delegate_mixin.dart} (69%) rename lib/src/mixin/{manup_dialog_mixin.dart => man_up_dialog_mixin.dart} (62%) rename lib/src/ui/{manup_app_dialog.dart => man_up_app_dialog.dart} (84%) create mode 100644 lib/src/ui/man_up_widget.dart delete mode 100644 lib/src/ui/manup_widget.dart diff --git a/.gitignore b/.gitignore index 0f3bf34..6e56e84 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ ios/Runner/GeneratedPluginRegistrant.* android/local.properties .vscode coverage +test/mandatory_update_test.mocks.dart # Flutter/Dart/Pub related **/doc/api/ diff --git a/.travis.yml b/.travis.yml index 6b9dd83..cdb376f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,8 @@ before_script: - ./.flutter/bin/flutter doctor script: + - ./.flutter/bin/flutter pub get + - ./.flutter/bin/flutter pub run build_runner build - ./.flutter/bin/flutter analyze - ./.flutter/bin/flutter test --coverage after_success: diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ec031..a1eec60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,44 @@ +# manUp + +## [2.0.1] + +Minor code changes. +Making some variables required. +Continue renaming some more files. + +## [2.0.0] + +Migrate to null safety. +**Breaking changes** Renaming some files. + +## [1.0.4] + +Make get setting method visible + ## [1.0.3] + Add support for desktop platforms + ## [1.0.2] + pub spec upgrade + Stop throwing reading file exception. + ## [1.0.1] + Add capability to store and retrieve man up config. + ## [1.0.0] + ### First major release -Added Delegate support with `ManupDelegate` + +Added Delegate support with `ManUpDelegate` + Added Helper Widget with `ManUpWidget` + Check `README` file for full details. + ## [0.0.3] + First manup release diff --git a/README.md b/README.md index c21fac0..4161419 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,10 @@ service.close(); ### Using the Service with Delegate -Implement `ManupDelegate` or use `ManupDelegateMixin` mixin which has default implementation. +Implement `ManUpDelegate` or use `ManUpDelegateMixin` mixin which has default implementation. - `manUpConfigUpdateStarting()` : will be called before starting to validate -- `manupStatusChanged(ManUpStatus status)` : will be called every time status changes +- `manUpStatusChanged(ManUpStatus status)` : will be called every time status changes - `manUpUpdateAvailable()` : will be called when ManUpStatus changes to supported - `manUpUpdateRequired()` : will be called when ManUpStatus changes to unsupported - `manUpMaintenanceMode()`: will be called when ManUpStatus changes to disabled @@ -70,7 +70,7 @@ class ManUpExample extends StatefulWidget { } class _ManUpExampleState extends State - with ManupDelegate, ManupDelegateMixin, DialogMixin { + with ManUpDelegate, ManUpDelegateMixin, DialogMixin { ManUpService service; @override void initState() { @@ -89,7 +89,7 @@ class _ManUpExampleState extends State @override void manUpStatusChanged(ManUpStatus status) { // handle status or show default dialog - showManupDialog(status, service.getMessage(forStatus: status), + showManUpDialog(status, service.getMessage(forStatus: status), service.configData.updateUrl); } @@ -102,7 +102,9 @@ class _ManUpExampleState extends State ``` ### Using the Service with Helper Widget -Wrap your widget with `ManUpWidget` to automaticaly handle every thing. + +Wrap your widget with `ManUpWidget` to automatically handle every thing. + ```dart @override Widget build(BuildContext context) { @@ -116,7 +118,8 @@ Wrap your widget with `ManUpWidget` to automaticaly handle every thing. ); } ``` + ### Exception Handling `validate` will throw a `ManUpException` if the lookup failed for any reason. Most likely, this will be caused -by the device being offline and unable to retreive the metadata. It is up to you how you want to handle this in your app. Some apps, where a supported version is critical, should probably not run unless the version was validated successfully. However, for other apps, there's probably no problem and the app should continue running. +by the device being offline and unable to retrieve the metadata. It is up to you how you want to handle this in your app. Some apps, where a supported version is critical, should probably not run unless the version was validated successfully. However, for other apps, there's probably no problem and the app should continue running. diff --git a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java deleted file mode 100644 index 80e9d08..0000000 --- a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.flutter.plugins; - -import io.flutter.plugin.common.PluginRegistry; -import dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin; -import io.flutter.plugins.pathprovider.PathProviderPlugin; -import io.flutter.plugins.urllauncher.UrlLauncherPlugin; - -/** - * Generated file. Do not edit. - */ -public final class GeneratedPluginRegistrant { - public static void registerWith(PluginRegistry registry) { - if (alreadyRegisteredWith(registry)) { - return; - } - PackageInfoPlugin.registerWith(registry.registrarFor("dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin")); - PathProviderPlugin.registerWith(registry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin")); - UrlLauncherPlugin.registerWith(registry.registrarFor("io.flutter.plugins.urllauncher.UrlLauncherPlugin")); - } - - private static boolean alreadyRegisteredWith(PluginRegistry registry) { - final String key = GeneratedPluginRegistrant.class.getCanonicalName(); - if (registry.hasPlugin(key)) { - return true; - } - registry.registrarFor(key); - return false; - } -} diff --git a/lib/manup.dart b/lib/manup.dart index 2aa9687..4aa9636 100644 --- a/lib/manup.dart +++ b/lib/manup.dart @@ -12,12 +12,12 @@ import 'package:url_launcher/url_launcher.dart'; part 'src/exception.dart'; part 'src/metadata.dart'; -part 'src/manup_status.dart'; -part 'src/manup_service.dart'; +part 'src/man_up_status.dart'; +part 'src/man_up_service.dart'; part 'src/package_info_provider.dart'; -part 'src/manup_delegate.dart'; -part 'src/ui/manup_app_dialog.dart'; -part 'src/ui/manup_widget.dart'; -part 'src/mixin/manup_delegate_mixin.dart'; -part 'src/mixin/manup_dialog_mixin.dart'; +part 'src/man_up_delegate.dart'; +part 'src/ui/man_up_app_dialog.dart'; +part 'src/ui/man_up_widget.dart'; +part 'src/mixin/man_up_delegate_mixin.dart'; +part 'src/mixin/man_up_dialog_mixin.dart'; part 'src/config_storage.dart'; diff --git a/lib/src/config_storage.dart b/lib/src/config_storage.dart index 20c1ed0..1de8658 100644 --- a/lib/src/config_storage.dart +++ b/lib/src/config_storage.dart @@ -1,16 +1,18 @@ part of manup; class ConfigStorage { - static const String _manupFile = "manup_config.json"; + const ConfigStorage(); + static const String _manUpFile = "man_up_config.json"; // - Future storeFile({String filename = _manupFile, String fileData}) { + Future storeFile( + {String filename = _manUpFile, required String fileData}) { return getApplicationDocumentsDirectory().then((directory) { final File file = File('${directory.path}/$filename'); return file.writeAsString(fileData).then((_) => Future.value(true)); }); } - Future readfile({String filename = _manupFile}) { + Future readFile({String filename = _manUpFile}) async { return getApplicationDocumentsDirectory().then((directory) { final File file = File('${directory.path}/$filename'); return file.exists().then((isExist) => diff --git a/lib/src/exception.dart b/lib/src/exception.dart index d920a1b..f72a395 100644 --- a/lib/src/exception.dart +++ b/lib/src/exception.dart @@ -1,7 +1,7 @@ part of manup; class ManUpException implements Exception { - final String msg; + final String? msg; const ManUpException([this.msg]); @override diff --git a/lib/src/manup_delegate.dart b/lib/src/man_up_delegate.dart similarity index 58% rename from lib/src/manup_delegate.dart rename to lib/src/man_up_delegate.dart index 05f8996..f35d28a 100644 --- a/lib/src/manup_delegate.dart +++ b/lib/src/man_up_delegate.dart @@ -1,8 +1,8 @@ part of manup; -/// `ManupDelegate` class has required methods. -/// Default implemetation is in `ManupDelegateMixin` file. -abstract class ManupDelegate { +/// `ManUpDelegate` class has required methods. +/// Default implementation is in `ManUpDelegateMixin` file. +abstract class ManUpDelegate { void manUpStatusChanged(ManUpStatus status); void manUpConfigUpdateStarting(); void manUpUpdateRequired(); diff --git a/lib/src/manup_service.dart b/lib/src/man_up_service.dart similarity index 58% rename from lib/src/manup_service.dart rename to lib/src/man_up_service.dart index 356c8ba..1347d4a 100644 --- a/lib/src/manup_service.dart +++ b/lib/src/man_up_service.dart @@ -3,27 +3,30 @@ part of manup; class ManUpService { final String url; final PackageInfoProvider packageInfoProvider; - @visibleForTesting - ConfigStorage fileStorage = ConfigStorage(); - String os; - Metadata _manupData; + final ConfigStorage fileStorage; + + final String os; + Metadata _manUpData = Metadata(); // read platform data - PlatformData get configData => this.getPlatformData(os, _manupData); - ManupDelegate delegate; + PlatformData? get configData => this.getPlatformData(os, _manUpData); + ManUpDelegate? delegate; - http.Client _client; + final http.Client _client; /// ManUpService( this.url, { this.packageInfoProvider = const DefaultPackageInfoProvider(), - this.os, - http.Client http, - }) : _client = http; + String? os, + required http.Client http, + ConfigStorage storage = const ConfigStorage(), + }) : _client = http, + fileStorage = storage, + this.os = os ?? Platform.operatingSystem; Future validate() async { - delegate?.manUpConfigUpdateStarting?.call(); + delegate?.manUpConfigUpdateStarting(); try { ManUpStatus status = await _validate(); this._handleManUpStatus(status); @@ -31,15 +34,19 @@ class ManUpService { } catch (e) { throw e; } finally { - _storeManupFile(); + await _storeManUpFile(); } } Future _validate() async { PackageInfo info = await this.packageInfoProvider.getInfo(); - _manupData = await this.getMetadata(); - - PlatformData platformData = configData; + final metadata = await this.getMetadata(); + _manUpData = metadata; + PlatformData? platformData = configData; + // + if (platformData == null) { + return ManUpStatus.unsupported; + } if (!platformData.enabled) { return ManUpStatus.disabled; } @@ -61,13 +68,10 @@ class ManUpService { } } - T setting({String key}) => _manupData?.setting(key: key) ?? null; + T? setting({String? key}) => _manUpData.setting(key: key) ?? null; @visibleForTesting - PlatformData getPlatformData(String os, Metadata data) { - if (data == null) { - throw ManUpException('No data, validate must be called first.'); - } + PlatformData? getPlatformData(String os, Metadata data) { if (os == 'ios') { return data.ios; } else if (os == 'android') { @@ -87,11 +91,11 @@ class ManUpService { try { final uri = Uri.parse(this.url); var data = await _client.get(uri); - Map json = jsonDecode(data.body); + Map? json = jsonDecode(data.body); return Metadata(data: json); } catch (exception) { try { - var metadata = await _readManupFile(); + var metadata = await _readManUpFile(); return metadata; } catch (e) { throw ManUpException(exception.toString()); @@ -99,63 +103,59 @@ class ManUpService { } } - // manup status validation - _handleManUpStatus(ManUpStatus status) { - this.delegate?.manUpStatusChanged?.call(status); + // manUp status validation + void _handleManUpStatus(ManUpStatus status) { + this.delegate?.manUpStatusChanged(status); switch (status) { case ManUpStatus.supported: - this.delegate?.manUpUpdateAvailable?.call(); + this.delegate?.manUpUpdateAvailable(); break; case ManUpStatus.unsupported: - this.delegate?.manUpUpdateRequired?.call(); + this.delegate?.manUpUpdateRequired(); break; case ManUpStatus.disabled: - this.delegate?.manUpMaintenanceMode?.call(); + this.delegate?.manUpMaintenanceMode(); break; default: break; } } - String getMessage({ManUpStatus forStatus}) { + String getMessage({required ManUpStatus forStatus}) { switch (forStatus) { case ManUpStatus.supported: - return _manupData?.supportedMessage; + return _manUpData.supportedMessage; case ManUpStatus.unsupported: - return _manupData?.unsupportedMessage; - break; + return _manUpData.unsupportedMessage; case ManUpStatus.disabled: - return _manupData?.disabledMessage; - break; - default: + return _manUpData.disabledMessage; + case ManUpStatus.latest: return ""; } } - /// manup file storage - void _storeManupFile() async { + /// manUp file storage + Future _storeManUpFile() async { try { - if (_manupData == null || _manupData._data == null) { + if (_manUpData._data == null) { return; } - String json = jsonEncode(_manupData._data); - fileStorage.storeFile(fileData: json); + String json = jsonEncode(_manUpData._data); + await fileStorage.storeFile(fileData: json); } catch (e) { print("cannot store file. $e"); } } - Future _readManupFile() async { - var data = await fileStorage.readfile(); - Map json = jsonDecode(data); + Future _readManUpFile() async { + var data = await fileStorage.readFile(); + Map? json = jsonDecode(data); return Metadata(data: json); } //call this on dispose. void close() { - _client?.close(); - _client = null; - fileStorage = null; + _client.close(); this.delegate = null; } } diff --git a/lib/src/manup_status.dart b/lib/src/man_up_status.dart similarity index 100% rename from lib/src/manup_status.dart rename to lib/src/man_up_status.dart diff --git a/lib/src/metadata.dart b/lib/src/metadata.dart index ae799b5..b0fdbed 100644 --- a/lib/src/metadata.dart +++ b/lib/src/metadata.dart @@ -15,7 +15,10 @@ class PlatformData { final String updateUrl; PlatformData( - {this.minVersion, this.latestVersion, this.enabled, this.updateUrl}); + {required this.minVersion, + required this.latestVersion, + required this.enabled, + required this.updateUrl}); @visibleForTesting static PlatformData fromData(Map data) { @@ -30,32 +33,39 @@ class PlatformData { /// Version information extracted from the JSON file class Metadata { /// ios specific configuration - PlatformData get ios => PlatformData.fromData(_data['ios']); + PlatformData? get ios => + _data?['ios'] != null ? PlatformData.fromData(_data!['ios']) : null; /// android specific configuration - PlatformData get android => PlatformData.fromData(_data['android']); + PlatformData? get android => _data?['android'] != null + ? PlatformData.fromData(_data!['android']) + : null; /// windows specific configuration - PlatformData get windows => PlatformData.fromData(_data['windows']); + PlatformData? get windows => _data?['windows'] != null + ? PlatformData.fromData(_data!['windows']) + : null; /// macos specific configuration - PlatformData get macos => PlatformData.fromData(_data['macos']); + PlatformData? get macos => + _data?['macos'] != null ? PlatformData.fromData(_data!['macos']) : null; /// linux specific configuration - PlatformData get linux => PlatformData.fromData(_data['linux']); + PlatformData? get linux => + _data?['linux'] != null ? PlatformData.fromData(_data!['linux']) : null; - // Configuration file data - final Map _data; - - dynamic rawSetting({String key}) => - _data.containsKey(key) ? _data[key] : null; + /// + /// + dynamic rawSetting({String? key}) => _data?[key] ?? null; - T setting({String key}) { + T? setting({String? key}) { var value = rawSetting(key: key); return value is T ? value : null; } - Metadata({Map data}) : _data = data; + // Configuration file data + final Map? _data; + Metadata({Map? data}) : _data = data; } // message extension diff --git a/lib/src/mixin/manup_delegate_mixin.dart b/lib/src/mixin/man_up_delegate_mixin.dart similarity index 69% rename from lib/src/mixin/manup_delegate_mixin.dart rename to lib/src/mixin/man_up_delegate_mixin.dart index 81a1bde..13ea0ca 100644 --- a/lib/src/mixin/manup_delegate_mixin.dart +++ b/lib/src/mixin/man_up_delegate_mixin.dart @@ -1,7 +1,7 @@ part of manup; -/// Default implemetation of [ManupDelegate] -mixin ManupDelegateMixin on ManupDelegate { +/// Default implementation of [ManUpDelegate] +mixin ManUpDelegateMixin on ManUpDelegate { void manUpStatusChanged(ManUpStatus status) {} void manUpConfigUpdateStarting() {} void manUpUpdateRequired() {} diff --git a/lib/src/mixin/manup_dialog_mixin.dart b/lib/src/mixin/man_up_dialog_mixin.dart similarity index 62% rename from lib/src/mixin/manup_dialog_mixin.dart rename to lib/src/mixin/man_up_dialog_mixin.dart index f261476..819ebad 100644 --- a/lib/src/mixin/manup_dialog_mixin.dart +++ b/lib/src/mixin/man_up_dialog_mixin.dart @@ -1,19 +1,19 @@ part of manup; -// Show app dialog based on manup status +// Show app dialog based on manUp status mixin DialogMixin on State { Future _launchUrl(String launchUrl) { - return canLaunch(launchUrl) - .then((canLaunch) => canLaunch ? launch(launchUrl) : false); + return canLaunch(launchUrl).then( + (canLaunch) => canLaunch ? launch(launchUrl) : Future.value(canLaunch)); } // It will emit `true` if updateLater is selected - Future showManupDialog( + Future showManUpDialog( ManUpStatus status, - String message, - String updateUrl, + String? message, + String? updateUrl, ) async { - ManupAppDialog _dialog = ManupAppDialog(); + ManUpAppDialog _dialog = ManUpAppDialog(); switch (status) { case ManUpStatus.latest: return Future.value(true); @@ -24,22 +24,21 @@ mixin DialogMixin on State { message: message, trueText: "Update", falseText: "Later") - .then( - (shouldUpdate) => shouldUpdate ? _launchUrl(updateUrl) : false) - .then((isLaunched) => !isLaunched); + .then((shouldUpdate) => + shouldUpdate! ? _launchUrl(updateUrl!) : false) + .then((isLaunched) => !(isLaunched as bool)); case ManUpStatus.unsupported: return _dialog .showAlertDialog( context: context, message: message, trueText: "Update") - .then((_) => _launchUrl(updateUrl)) + .then((_) => _launchUrl(updateUrl!)) .then((_) => false); case ManUpStatus.disabled: return _dialog .showAlertDialog(context: context, message: message) .then((_) => exit(0)) - .then((_) => false); + .then(((_) => false)); } - throw ManUpException("Unknown manup status"); } } diff --git a/lib/src/ui/manup_app_dialog.dart b/lib/src/ui/man_up_app_dialog.dart similarity index 84% rename from lib/src/ui/manup_app_dialog.dart rename to lib/src/ui/man_up_app_dialog.dart index 7ee6cd6..1dd48aa 100644 --- a/lib/src/ui/manup_app_dialog.dart +++ b/lib/src/ui/man_up_app_dialog.dart @@ -1,12 +1,12 @@ part of manup; -class ManupAppDialog { - Future showAlertDialog( - {String message = " ", +class ManUpAppDialog { + Future showAlertDialog( + {String? message, String trueText = "ok", - String falseText, + String? falseText, bool barrierDismissible = false, - @required BuildContext context}) { + required BuildContext context}) { bool hasCancelText = falseText != null && falseText.isNotEmpty; return showDialog( barrierDismissible: barrierDismissible, @@ -15,7 +15,7 @@ class ManupAppDialog { return WillPopScope( onWillPop: () => Future.value(barrierDismissible), child: AlertDialog( - title: Text(message), + title: Text(message ?? ""), actions: [ hasCancelText ? TextButton( diff --git a/lib/src/ui/man_up_widget.dart b/lib/src/ui/man_up_widget.dart new file mode 100644 index 0000000..98beba0 --- /dev/null +++ b/lib/src/ui/man_up_widget.dart @@ -0,0 +1,82 @@ +part of manup; + +class ManUpWidget extends StatefulWidget { + final ManUpService service; + final Widget child; + final bool Function() shouldShowAlert; + final void Function(bool) onComplete; + final ManUpStatus Function(dynamic) onError; + // + ManUpWidget( + {Key? key, + required this.child, + required this.service, + required this.shouldShowAlert, + required this.onComplete, + required this.onError}) + : super(key: key); + + @override + _ManUpWidgetState createState() => _ManUpWidgetState(); +} + +/// +/// +class _ManUpWidgetState extends State + with + ManUpDelegate, + ManUpDelegateMixin, + DialogMixin, + WidgetsBindingObserver { + bool isShowingManUpAlert = false; + // + @override + void initState() { + super.initState(); + widget.service.delegate = this; + validateManUp(); + WidgetsBinding.instance?.addObserver(this); + } + + @override + Widget build(BuildContext context) => widget.child; + + validateManUp() { + widget.service.validate().catchError((e) => widget.onError(e)); + } + + // Man up + bool get shouldShowManUpAlert => this.widget.shouldShowAlert.call(); + // man up delegate + @override + void manUpStatusChanged(ManUpStatus status) { + if (status == ManUpStatus.latest) { + this.widget.onComplete.call(true); + return; + } + final updateUrl = widget.service.configData?.updateUrl; + if (this.shouldShowManUpAlert && updateUrl != null) { + final message = widget.service.getMessage(forStatus: status); + isShowingManUpAlert = true; + showManUpDialog(status, message, updateUrl) + .then((isUpdateLater) => + isUpdateLater ? this.widget.onComplete.call(true) : isUpdateLater) + .then((_) => isShowingManUpAlert = false); + } + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (!isShowingManUpAlert && state == AppLifecycleState.resumed) { + validateManUp(); + } + } + +// + @override + void dispose() { + widget.service.close(); + WidgetsBinding.instance?.removeObserver(this); + super.dispose(); + } +} diff --git a/lib/src/ui/manup_widget.dart b/lib/src/ui/manup_widget.dart deleted file mode 100644 index b24dd4f..0000000 --- a/lib/src/ui/manup_widget.dart +++ /dev/null @@ -1,81 +0,0 @@ -part of manup; - -class ManUpWidget extends StatefulWidget { - final ManUpService service; - final Widget child; - final bool Function() shouldShowAlert; - final void Function(bool) onComplete; - final ManUpStatus Function(dynamic) onError; - // - ManUpWidget( - {Key key, - @required this.child, - @required this.service, - @required this.shouldShowAlert, - @required this.onComplete, - @required this.onError}) - : super(key: key); - - @override - _ManUpWidgetState createState() => _ManUpWidgetState(); -} - -class _ManUpWidgetState extends State - with - ManupDelegate, - ManupDelegateMixin, - DialogMixin, - WidgetsBindingObserver { - bool isshowingManupAlert = false; - // - @override - void initState() { - super.initState(); - widget.service.delegate = this; - validateManup(); - WidgetsBinding.instance?.addObserver(this); - } - - @override - Widget build(BuildContext context) => widget?.child; - - validateManup() { - widget.service.validate().catchError((e) => widget?.onError(e)); - } - - // Man up - bool get shouldShowManupAlert => - this?.widget?.shouldShowAlert?.call() ?? true; - // man up delegate - @override - void manUpStatusChanged(ManUpStatus status) { - if (status == ManUpStatus.latest) { - this.widget?.onComplete?.call(true); - return; - } - if (this.shouldShowManupAlert) { - isshowingManupAlert = true; - showManupDialog(status, widget.service.getMessage(forStatus: status), - widget.service.configData.updateUrl) - .then((isUpdateLater) => isUpdateLater - ? this.widget?.onComplete?.call(true) - : isUpdateLater) - .then((_) => isshowingManupAlert = false); - } - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (!isshowingManupAlert && state == AppLifecycleState.resumed) { - validateManup(); - } - } - -// - @override - void dispose() { - widget?.service?.close(); - WidgetsBinding.instance?.removeObserver(this); - super.dispose(); - } -} diff --git a/pubspec.lock b/pubspec.lock index 51c8a5f..21c5d77 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -43,6 +43,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.0.0" built_collection: dependency: transitive description: @@ -71,6 +106,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" cli_util: dependency: transitive description: @@ -163,6 +205,13 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" glob: dependency: transitive description: @@ -170,6 +219,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" http: dependency: "direct main" description: @@ -177,6 +233,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.13.3" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" http_parser: dependency: transitive description: @@ -184,6 +247,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" js: dependency: transitive description: @@ -191,6 +261,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" logging: dependency: transitive description: @@ -212,6 +289,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" mockito: dependency: "direct dev" description: @@ -331,6 +415,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0" process: dependency: transitive description: @@ -345,6 +436,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" sky_engine: dependency: transitive description: flutter @@ -378,6 +490,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" string_scanner: dependency: transitive description: @@ -399,6 +518,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.19" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" typed_data: dependency: transitive description: @@ -462,6 +588,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 041f967..597bb33 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: manup description: Mandatory update for Flutter Apps. -version: 1.0.4 +version: 2.0.1 homepage: https://github.com/NextFaze/flutter_manup environment: - sdk: ">=2.6.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: @@ -20,6 +20,4 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.7 - - -flutter: + build_runner: any diff --git a/test/mandatory_update_test.dart b/test/mandatory_update_test.dart index cefe8a9..a7b2fd3 100644 --- a/test/mandatory_update_test.dart +++ b/test/mandatory_update_test.dart @@ -1,4 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; +import 'package:http/testing.dart'; +import 'package:mockito/annotations.dart'; import 'dart:convert'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:mockito/mockito.dart'; @@ -6,9 +8,17 @@ import 'package:http/http.dart' as http; import 'package:manup/manup.dart'; -class MockClient extends Mock implements http.Client {} +import 'mandatory_update_test.mocks.dart' as Mocks; +//////////////////////////////////////////////////////////////////////// +// // +// // +// RUN `flutter pub run build_runner build` ON Terminal // +// // +// // +//////////////////////////////////////////////////////////////////////// var osGetter = () => "ios"; +const String _manUpFile = "man_up_config.json"; class MockPackageInfo extends PackageInfoProvider { String version; @@ -23,19 +33,14 @@ class MockPackageInfo extends PackageInfoProvider { } } -class MockConfigStorage extends Mock implements ConfigStorage {} - +/// +// Generate a MockClient using the Mockito package. +// Create new instances of this class in each test. +@GenerateMocks([ConfigStorage]) void main() { group('ManUpService', () { - final mockfilestorage = MockConfigStorage(); + final mockFileStorage = Mocks.MockConfigStorage(); - setUp(() { - when(mockfilestorage.storeFile( - fileData: anyNamed("fileData"), filename: "manup_config.json")) - .thenAnswer((_) => Future.value(true)); - when(mockfilestorage.readfile(filename: "manup_config.json")) - .thenThrow(Exception("unwated call")); - }); test('parseJson converts to a PlatformData object', () { Map json = jsonDecode(''' { @@ -53,8 +58,15 @@ void main() { }); group('getMetadata', () { + http.Client client; + + setUp(() { + when(mockFileStorage.storeFile( + filename: _manUpFile, fileData: anyNamed('fileData'))) + .thenAnswer((_) => Future.value(true)); + }); + test('It fetches and returns metadata', () async { - var client = MockClient(); var response = http.Response(''' { "ios": { @@ -71,31 +83,43 @@ void main() { } } ''', 200); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenAnswer((Invocation i) => Future.value(response)); + client = MockClient((r) => Future.value(response)); var service = ManUpService('https://example.com/manup.json', - http: client, os: osGetter()); - service.fileStorage = mockfilestorage; + http: client, os: osGetter(), storage: mockFileStorage); var metadata = await service.getMetadata(); - verify(client.get(Uri.parse("https://example.com/manup.json"))) - .called(1); - expect(metadata.ios.enabled, true); - expect(metadata.ios.latestVersion, "2.4.1"); - expect(metadata.ios.minVersion, "2.1.0"); - expect(metadata.ios.updateUrl, "http://example.com/myAppUpdate"); - - expect(metadata.android.enabled, false); - expect(metadata.android.latestVersion, "2.5.1"); - expect(metadata.android.minVersion, "1.9.0"); - expect(metadata.android.updateUrl, + expect(metadata.ios != null, true); + var iosMetaData = metadata.ios!; + expect(iosMetaData.enabled, true); + expect(iosMetaData.latestVersion, "2.4.1"); + expect(iosMetaData.minVersion, "2.1.0"); + expect(iosMetaData.updateUrl, "http://example.com/myAppUpdate"); + // + expect(metadata.android != null, true); + var androidMetaData = metadata.android!; + expect(androidMetaData.enabled, false); + expect(androidMetaData.latestVersion, "2.5.1"); + expect(androidMetaData.minVersion, "1.9.0"); + expect(androidMetaData.updateUrl, "http://example.com/myAppUpdate/android"); + // + expect(metadata.windows, null); + expect(metadata.macos, null); + expect(metadata.linux, null); + // + expect(metadata.rawSetting(key: "ios") != null, true); + expect(metadata.rawSetting(key: "windows"), null); + expect(metadata.rawSetting(key: "anything"), null); + // + expect( + metadata.setting>(key: "ios") != null, true); + expect(metadata.setting(key: "ios"), null); }); test('Read custom properties from configuration', () async { var packageInfo = MockPackageInfo("1.1.0"); - var client = MockClient(); + MockClient client; var response = http.Response(''' { "ios": { @@ -114,15 +138,16 @@ void main() { "number-of-coins": 12 } ''', 200); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenAnswer((Invocation i) => Future.value(response)); + client = MockClient((r) => Future.value(response)); + var service = ManUpService('https://example.com/manup.json', - packageInfoProvider: packageInfo, http: client, os: osGetter()); - service.fileStorage = mockfilestorage; - await service.validate(); - verify(client.get(Uri.parse("https://example.com/manup.json"))) - .called(1); + packageInfoProvider: packageInfo, + http: client, + os: osGetter(), + storage: mockFileStorage); + await service.validate(); + // expect(service.setting(key: "api-base"), "http://api.example.com/"); expect(service.setting(key: "api-base"), null); @@ -136,7 +161,7 @@ void main() { group("validate", () { test('an unsupported version', () async { var packageInfo = MockPackageInfo("1.1.0"); - var client = MockClient(); + var client; var response = http.Response(''' { "ios": { @@ -153,18 +178,18 @@ void main() { } } ''', 200); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenAnswer((Invocation i) => Future.value(response)); - + client = MockClient((r) => Future.value(response)); var service = ManUpService('https://example.com/manup.json', - packageInfoProvider: packageInfo, http: client, os: osGetter()); - service.fileStorage = mockfilestorage; + packageInfoProvider: packageInfo, + http: client, + os: osGetter(), + storage: mockFileStorage); var result = await service.validate(); expect(result, ManUpStatus.unsupported); }); test('the minimum version version', () async { var packageInfo = MockPackageInfo("2.1.0"); - var client = MockClient(); + var client; var response = http.Response(''' { "ios": { @@ -181,18 +206,19 @@ void main() { } } ''', 200); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenAnswer((Invocation i) => Future.value(response)); - + client = MockClient((r) => Future.value(response)); var service = ManUpService('https://example.com/manup.json', - packageInfoProvider: packageInfo, http: client, os: osGetter()); - service.fileStorage = mockfilestorage; + packageInfoProvider: packageInfo, + http: client, + os: osGetter(), + storage: mockFileStorage); + var result = await service.validate(); expect(result, ManUpStatus.supported); }); test('some supported version', () async { var packageInfo = MockPackageInfo("2.3.3"); - var client = MockClient(); + var client; var response = http.Response(''' { "ios": { @@ -203,18 +229,18 @@ void main() { } } ''', 200); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenAnswer((Invocation i) => Future.value(response)); - + client = MockClient((r) => Future.value(response)); var service = ManUpService('https://example.com/manup.json', - packageInfoProvider: packageInfo, http: client, os: osGetter()); - service.fileStorage = mockfilestorage; + packageInfoProvider: packageInfo, + http: client, + os: osGetter(), + storage: mockFileStorage); + var result = await service.validate(); expect(result, ManUpStatus.supported); }); test('the latest version', () async { var packageInfo = MockPackageInfo("2.4.1"); - var client = MockClient(); var response = http.Response(''' { "ios": { @@ -225,18 +251,17 @@ void main() { } } ''', 200); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenAnswer((Invocation i) => Future.value(response)); - + var client = MockClient((r) => Future.value(response)); var service = ManUpService('https://example.com/manup.json', - packageInfoProvider: packageInfo, http: client, os: osGetter()); - service.fileStorage = mockfilestorage; + packageInfoProvider: packageInfo, + http: client, + os: osGetter(), + storage: mockFileStorage); var result = await service.validate(); expect(result, ManUpStatus.latest); }); test('allow greater than latest version', () async { var packageInfo = MockPackageInfo("3.4.1"); - var client = MockClient(); var response = http.Response(''' { "ios": { @@ -247,18 +272,17 @@ void main() { } } ''', 200); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenAnswer((Invocation i) => Future.value(response)); - + var client = MockClient((r) => Future.value(response)); var service = ManUpService('https://example.com/manup.json', - packageInfoProvider: packageInfo, http: client, os: osGetter()); - service.fileStorage = mockfilestorage; + packageInfoProvider: packageInfo, + http: client, + os: osGetter(), + storage: mockFileStorage); var result = await service.validate(); expect(result, ManUpStatus.latest); }); test('marked as disabled', () async { var packageInfo = MockPackageInfo("2.4.1"); - var client = MockClient(); var response = http.Response(''' { "ios": { @@ -269,40 +293,39 @@ void main() { } } ''', 200); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenAnswer((Invocation i) => Future.value(response)); + var client = MockClient((r) => Future.value(response)); var service = ManUpService('https://example.com/manup.json', - packageInfoProvider: packageInfo, http: client, os: osGetter()); - service.fileStorage = mockfilestorage; + packageInfoProvider: packageInfo, + http: client, + os: osGetter(), + storage: mockFileStorage); var result = await service.validate(); expect(result, ManUpStatus.disabled); }); test('throws an exception if the lookup failed', () async { var packageInfo = MockPackageInfo("2.4.1"); - var client = MockClient(); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenThrow(Exception('test error')); + var client = MockClient((r) => Future.error(Exception("text error"))); var service = ManUpService('https://example.com/manup.json', - packageInfoProvider: packageInfo, http: client, os: osGetter()); - service.fileStorage = mockfilestorage; + packageInfoProvider: packageInfo, + http: client, + os: osGetter(), + storage: mockFileStorage); expect(() => service.validate(), throwsException); }); }); }); group("ManUpService: store service", () { - final mockfilestorage = MockConfigStorage(); - setUp(() {}); + final mockFileStorage = Mocks.MockConfigStorage(); test('store file should get call', () async { - when(mockfilestorage.storeFile( - fileData: anyNamed("fileData"), filename: "manup_config.json")) + //mockFileStorage.stx + when(mockFileStorage.storeFile( + filename: _manUpFile, fileData: anyNamed('fileData'))) .thenAnswer((_) => Future.value(true)); - when(mockfilestorage.readfile(filename: "manup_config.json")) - .thenThrow(Exception("unwated call")); + var packageInfo = MockPackageInfo("2.4.1"); - var client = MockClient(); var response = http.Response(''' { "ios": { @@ -313,24 +336,28 @@ void main() { } } ''', 200); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenAnswer((Invocation i) => Future.value(response)); + var client = MockClient((r) => Future.value(response)); + var service = ManUpService('https://example.com/manup.json', - packageInfoProvider: packageInfo, http: client, os: osGetter()); - service.fileStorage = mockfilestorage; + packageInfoProvider: packageInfo, + http: client, + os: osGetter(), + storage: mockFileStorage); + var result = await service.validate(); expect(result, ManUpStatus.latest); // - verify(mockfilestorage.storeFile( - fileData: anyNamed("fileData"), filename: "manup_config.json")) + verify(mockFileStorage.storeFile( + fileData: anyNamed('fileData'), filename: _manUpFile)) .called(1); }); test('read file should get call', () async { - when(mockfilestorage.storeFile( - fileData: anyNamed("fileData"), filename: "manup_config.json")) + when(mockFileStorage.storeFile( + filename: _manUpFile, fileData: anyNamed('fileData'))) .thenAnswer((_) => Future.value(true)); - when(mockfilestorage.readfile(filename: "manup_config.json")) - .thenAnswer((_) => Future.value(''' + when(mockFileStorage.readFile(filename: _manUpFile)) + .thenAnswer((_) async { + return ''' { "ios": { "latest": "2.4.1", @@ -339,20 +366,27 @@ void main() { "enabled": true } } - ''')); + '''; + }); var packageInfo = MockPackageInfo("2.4.1"); - var client = MockClient(); var response = http.Response('', 500); - when(client.get(Uri.parse("https://example.com/manup.json"))) - .thenAnswer((Invocation i) => Future.value(response)); + var client = MockClient((r) => Future.value(response)); var service = ManUpService('https://example.com/manup.json', - packageInfoProvider: packageInfo, http: client, os: osGetter()); - service.fileStorage = mockfilestorage; + packageInfoProvider: packageInfo, + http: client, + os: osGetter(), + storage: mockFileStorage); + // pre validation test + expect(service.configData, null); + + /// var result = await service.validate(); + // post validation test expect(result, ManUpStatus.latest); + expect(service.configData != null, true); // - verify(mockfilestorage.readfile(filename: "manup_config.json")).called(1); + verify(mockFileStorage.readFile(filename: _manUpFile)).called(1); }); }); }