From d1743a9a5ae817820bf115bb21a407cf7c5a3757 Mon Sep 17 00:00:00 2001 From: roshani-prajapati-us Date: Fri, 15 Sep 2023 18:23:04 +0530 Subject: [PATCH 1/2] - Fetch and parce data in model directly in to package - return model class instead of Map and also create class which directly parce the data in to the package. --- example/lib/example.dart | 36 +++++-- lib/apps_on_air_service.dart | 9 +- ...ter_app_update_package_method_channel.dart | 37 +++++-- ...app_update_package_platform_interface.dart | 4 +- lib/src/model/app_info_model.dart | 100 ++++++++++++++++++ 5 files changed, 164 insertions(+), 22 deletions(-) create mode 100644 lib/src/model/app_info_model.dart diff --git a/example/lib/example.dart b/example/lib/example.dart index fb1e23e..3612d56 100644 --- a/example/lib/example.dart +++ b/example/lib/example.dart @@ -46,21 +46,41 @@ class DemoAppp extends StatefulWidget { class _DemoApppState extends State { @override void initState() { + AppsOnAir.checkForAppUpdate( context, - ///use customWidget only if you want to use Your custom ui, ///make sure to pass false in param [showNativeUI] - customWidget: (response) { - return Column(children: [ - Text("Application Name : ${response["appName"]}"), - Text( - "Application Version : ${response["updateData"]["androidBuildNumber"]}", + customWidget: + + (response) { + return + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + + Padding( + padding: const EdgeInsets.only(top: 20), + child: Text("${response.appName} : need an update "), ), - MaterialButton( - onPressed: () {}, + + Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + "To use this app, download the latest version:${response.updateData?.androidBuildNumber}", + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton( + onPressed: () {}, child: const Text("Update"), + ), + ], ) ]); + }, ); super.initState(); diff --git a/lib/apps_on_air_service.dart b/lib/apps_on_air_service.dart index bbd55e5..a564d7d 100644 --- a/lib/apps_on_air_service.dart +++ b/lib/apps_on_air_service.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'flutter_app_update_package_platform_interface.dart'; +import 'src/model/app_info_model.dart'; class AppsOnAir { /// @@ -25,11 +26,13 @@ class AppsOnAir { static setAppId(String appId, {bool showNativeUI = false}) { _appId = appId; _showNativeUI = showNativeUI; + } static void checkForAppUpdate( BuildContext context, { - Widget? Function(Map)? customWidget, + Widget? Function(AppInfo)? customWidget, + }) { if (_appId.isNotEmpty) { if (!_showNativeUI && customWidget == null) { @@ -37,6 +40,7 @@ class AppsOnAir { "set showNativeUI = 'true' in setAppId()" " or/else return your custom widget in checkForAppUpdate() Method ", ); + } else if (_showNativeUI && customWidget != null) { _printWarning( "set showNativeUI = 'false' to show custom ui in setAppId() or/else remove custom widget from checkForAppUpdate() method"); @@ -44,7 +48,8 @@ class AppsOnAir { FlutterAppUpdatePackagePlatform.instance.initMethod(context, appId: _appId, showNativeUI: _showNativeUI, - customWidget: customWidget); + customWidget: customWidget, + ); } else { throw Exception( "Make sure you initialized AppsOnAir by calling initialize() method"); diff --git a/lib/flutter_app_update_package_method_channel.dart b/lib/flutter_app_update_package_method_channel.dart index 2544641..a847692 100644 --- a/lib/flutter_app_update_package_method_channel.dart +++ b/lib/flutter_app_update_package_method_channel.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'flutter_app_update_package_platform_interface.dart'; +import 'src/model/app_info_model.dart'; /// An implementation of [FlutterAppUpdatePackagePlatform] that uses method channels. class MethodChannelFlutterAppUpdatePackage @@ -25,18 +26,19 @@ class MethodChannelFlutterAppUpdatePackage BuildContext context, { required String appId, bool showNativeUI = false, - Widget? Function(Map response)? customWidget, + Widget? Function(AppInfo response)? customWidget, }) async { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { this.context = context; final result = await methodChannel.invokeMethod( 'setApplicationID', {"AppId": appId, "showNativeUI": showNativeUI}); if (result) { + _listenToNativeMethod(); final appUpdateResponse = await _check(); if (customWidget != null) { final widget = customWidget.call(appUpdateResponse); - + ///custom ui dialog if (!showNativeUI && widget != null) { _alertDialog(widget); @@ -46,7 +48,9 @@ class MethodChannelFlutterAppUpdatePackage }); } + void _listenToNativeMethod() { + if (Platform.isIOS) { methodChannel.setMethodCallHandler((call) { switch (call.method) { @@ -55,20 +59,25 @@ class MethodChannelFlutterAppUpdatePackage break; case "closeDialog": if (_dialogOpen) { + _dialogOpen = false; _closeDialog(); } } return Future.sync(() => _dialogOpen); - }); + }, + ); } } + // while native dialog is open (in IOS), Flutter ui is still accessible // This dialog is solution for to prevent flutter ui access void _showIgnorePointerDialog() { + if (!_dialogOpen) { _dialogOpen = true; + showDialog( context: context, barrierDismissible: false, @@ -78,21 +87,24 @@ class MethodChannelFlutterAppUpdatePackage color: Colors.transparent, ), ); - } + } } void _closeDialog() => Navigator.pop(context); - void _alertDialog(Widget widget) { + void _alertDialog(Widget widget, ) { + showDialog( context: context, barrierDismissible: false, builder: (context) { + return WillPopScope( onWillPop: () async { return false; }, - child: AlertDialog( + child: + AlertDialog( content: widget, ), ); @@ -100,22 +112,25 @@ class MethodChannelFlutterAppUpdatePackage ); } - Future> _check() async { + + + Future _check() async { String updateCheck = ''; try { final result = await methodChannel.invokeMethod( 'isUpdateAvailable', ); + if (result != null && result is String) { - return Map.from(json.decode(result)); + return AppInfo.fromJson(json.decode(result)); } - return Map.from(((result ?? {}) as Map)); + return AppInfo.fromJson((result ?? {}) as Map); } on PlatformException catch (e) { updateCheck = "Failed to check for update: '${e.message}'."; } if (kDebugMode) { - print(updateCheck); + print(AppInfo(updateData: UpdateData(updateCheck: updateCheck))); } - return {"exception": updateCheck}; + return AppInfo(updateData: UpdateData(updateCheck: updateCheck)); } } diff --git a/lib/flutter_app_update_package_platform_interface.dart b/lib/flutter_app_update_package_platform_interface.dart index acf97b1..0f22ab9 100644 --- a/lib/flutter_app_update_package_platform_interface.dart +++ b/lib/flutter_app_update_package_platform_interface.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'flutter_app_update_package_method_channel.dart'; +import 'src/model/app_info_model.dart'; abstract class FlutterAppUpdatePackagePlatform extends PlatformInterface { /// Constructs a FlutterAppUpdatePackagePlatform. @@ -37,7 +38,8 @@ abstract class FlutterAppUpdatePackagePlatform extends PlatformInterface { BuildContext context, { required String appId, bool showNativeUI = true, - Widget? Function(Map)? customWidget, + Widget? Function(AppInfo)? customWidget, + }) { throw UnimplementedError('platformVersion() has not been implemented.'); } diff --git a/lib/src/model/app_info_model.dart b/lib/src/model/app_info_model.dart new file mode 100644 index 0000000..01565c8 --- /dev/null +++ b/lib/src/model/app_info_model.dart @@ -0,0 +1,100 @@ + +class AppInfo { + String? appName; + String? appLogo; + String? id; + bool? isMaintenance; + UpdateData? updateData; + MaintenanceData? maintenanceData; + + AppInfo({ + this.appName, + this.appLogo, + this.id, + this.isMaintenance, + this.updateData, + this.maintenanceData, + }); + + factory AppInfo.fromJson(Map json) => AppInfo( + appName: json["appName"], + appLogo: json["appLogo"], + id: json["id"], + isMaintenance: json["isMaintenance"], + updateData: json["updateData"] == null ? null : UpdateData.fromJson(json["updateData"]), + maintenanceData: json["maintenanceData"] == null ? null : MaintenanceData.fromJson(json["maintenanceData"]), + ); + + Map toJson() => { + "appName": appName, + "appLogo": appLogo, + "id": id, + "isMaintenance": isMaintenance, + "updateData": updateData?.toJson(), + "maintenanceData": maintenanceData?.toJson(), + }; +} + +class MaintenanceData { + MaintenanceData(); + + factory MaintenanceData.fromJson(Map json) => MaintenanceData(); + + Map toJson() => {}; +} + +class UpdateData { + bool? isIosUpdate; + String? iosBuildNumber; + String? iosMinBuildVersion; + String? iosUpdateLink; + bool? isIosForcedUpdate; + bool? isAndroidUpdate; + String? androidBuildNumber; + String? androidMinBuildVersion; + String? androidUpdateLink; + bool? isAndroidForcedUpdate; + String? updateCheck; + + UpdateData({ + this.isIosUpdate, + this.iosBuildNumber, + this.iosMinBuildVersion, + this.iosUpdateLink, + this.isIosForcedUpdate, + this.isAndroidUpdate, + this.androidBuildNumber, + this.androidMinBuildVersion, + this.androidUpdateLink, + this.isAndroidForcedUpdate, + this.updateCheck, + }); + + factory UpdateData.fromJson(Map json) => UpdateData( + isIosUpdate: json["isIOSUpdate"], + iosBuildNumber: json["iosBuildNumber"], + iosMinBuildVersion: json["iosMinBuildVersion"], + iosUpdateLink: json["iosUpdateLink"], + isIosForcedUpdate: json["isIOSForcedUpdate"], + isAndroidUpdate: json["isAndroidUpdate"], + androidBuildNumber: json["androidBuildNumber"], + androidMinBuildVersion: json["androidMinBuildVersion"], + androidUpdateLink: json["androidUpdateLink"], + isAndroidForcedUpdate: json["isAndroidForcedUpdate"], + updateCheck: json['exception'], + ); + + Map toJson() => { + "isIOSUpdate": isIosUpdate, + "iosBuildNumber": iosBuildNumber, + "iosMinBuildVersion": iosMinBuildVersion, + "iosUpdateLink": iosUpdateLink, + "isIOSForcedUpdate": isIosForcedUpdate, + "isAndroidUpdate": isAndroidUpdate, + "androidBuildNumber": androidBuildNumber, + "androidMinBuildVersion": androidMinBuildVersion, + "androidUpdateLink": androidUpdateLink, + "isAndroidForcedUpdate": isAndroidForcedUpdate, + "exception" : updateCheck, + }; +} From 2a94ff99eb2f9d7f3eaf3c876246ac7078540537 Mon Sep 17 00:00:00 2001 From: roshani-prajapati-us Date: Tue, 19 Sep 2023 17:34:04 +0530 Subject: [PATCH 2/2] =?UTF-8?q?-=20Android=20version=C2=A0=20IOS=20version?= =?UTF-8?q?=20available=20check=20Android=20update=20available=20check.=20?= =?UTF-8?q?make=20dialog=20radius=20rounded=20(custom=20ui)=20can=20change?= =?UTF-8?q?=20padding=20of=20custom=20ui=20show=20Dialog=20Platform=20spec?= =?UTF-8?q?ifically?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/lib/example.dart | 36 +++++++----- lib/apps_on_air_service.dart | 10 +++- ...ter_app_update_package_method_channel.dart | 57 +++++++++++++++---- ...app_update_package_platform_interface.dart | 3 + lib/src/model/app_info_model.dart | 14 ++++- 5 files changed, 89 insertions(+), 31 deletions(-) diff --git a/example/lib/example.dart b/example/lib/example.dart index 3612d56..03cd380 100644 --- a/example/lib/example.dart +++ b/example/lib/example.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:appsonair_flutter_sdk/apps_on_air_service.dart'; import 'package:flutter/material.dart'; @@ -22,7 +24,6 @@ class _MyAppState extends State { } // Platform messages are asynchronous, so we initialize in an async method. - @override Widget build(BuildContext context) { return MaterialApp( @@ -49,26 +50,33 @@ class _DemoApppState extends State { AppsOnAir.checkForAppUpdate( context, - ///use customWidget only if you want to use Your custom ui, - ///make sure to pass false in param [showNativeUI] - customWidget: + padding: const EdgeInsets.all(15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + + ///use customWidget only if you want to use Your custom ui, + ///make sure to pass false in param [showNativeUI] + + customWidget: (response) { + return + - (response) { - return - Column( + Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.only(top: 20), - child: Text("${response.appName} : need an update "), + child: Text("${response.appName } : need an update "), ), Padding( - padding: const EdgeInsets.symmetric(vertical: 15), - child: Text( - "To use this app, download the latest version:${response.updateData?.androidBuildNumber}", + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text(Platform.isAndroid ? + + "To use this app, download the latest version:${response.updateData?.androidBuildNumber}":"To use this app, download the latest version:${response.updateData?.iosBuildNumber}" ), ), Row( @@ -80,10 +88,10 @@ class _DemoApppState extends State { ], ) ]); - - }, - ); + }, + ); super.initState(); + } @override diff --git a/lib/apps_on_air_service.dart b/lib/apps_on_air_service.dart index a564d7d..bf5390b 100644 --- a/lib/apps_on_air_service.dart +++ b/lib/apps_on_air_service.dart @@ -32,6 +32,9 @@ class AppsOnAir { static void checkForAppUpdate( BuildContext context, { Widget? Function(AppInfo)? customWidget, + EdgeInsetsGeometry? padding, + ShapeBorder? shape + }) { if (_appId.isNotEmpty) { @@ -45,10 +48,13 @@ class AppsOnAir { _printWarning( "set showNativeUI = 'false' to show custom ui in setAppId() or/else remove custom widget from checkForAppUpdate() method"); } - FlutterAppUpdatePackagePlatform.instance.initMethod(context, + FlutterAppUpdatePackagePlatform.instance.initMethod( + context, appId: _appId, showNativeUI: _showNativeUI, - customWidget: customWidget, + customWidget: customWidget , + padding: padding, + shape: shape ); } else { throw Exception( diff --git a/lib/flutter_app_update_package_method_channel.dart b/lib/flutter_app_update_package_method_channel.dart index a847692..933fa15 100644 --- a/lib/flutter_app_update_package_method_channel.dart +++ b/lib/flutter_app_update_package_method_channel.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; - import 'flutter_app_update_package_platform_interface.dart'; import 'src/model/app_info_model.dart'; @@ -21,15 +20,20 @@ class MethodChannelFlutterAppUpdatePackage ///[isShowNative] is used to show [customWidget] ///[isShowNative] turn it off if you want to show your [customWidget] /// you can show your [customWidget] ui + @override Future initMethod( BuildContext context, { required String appId, bool showNativeUI = false, Widget? Function(AppInfo response)? customWidget, + EdgeInsetsGeometry? padding, + ShapeBorder? shape + }) async { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { this.context = context; + final result = await methodChannel.invokeMethod( 'setApplicationID', {"AppId": appId, "showNativeUI": showNativeUI}); if (result) { @@ -37,18 +41,36 @@ class MethodChannelFlutterAppUpdatePackage _listenToNativeMethod(); final appUpdateResponse = await _check(); if (customWidget != null) { + + final widget = customWidget.call(appUpdateResponse); ///custom ui dialog if (!showNativeUI && widget != null) { - _alertDialog(widget); - } + if(Platform.isAndroid){ + if(appUpdateResponse.updateData?.isAndroidForcedUpdate == true || appUpdateResponse.updateData?.isAndroidUpdate == true){ + _alertDialog(widget, + padding: padding, shape: shape); + } + }else{ + if(appUpdateResponse.updateData?.isIosForcedUpdate == true || appUpdateResponse.updateData?.isIosUpdate == true){ + _alertDialog(widget, + padding: padding, shape: shape); + + } + } + } + + } } }); } + + + void _listenToNativeMethod() { if (Platform.isIOS) { @@ -92,39 +114,49 @@ class MethodChannelFlutterAppUpdatePackage void _closeDialog() => Navigator.pop(context); - void _alertDialog(Widget widget, ) { - + void _alertDialog(Widget widget, {required EdgeInsetsGeometry? padding,required ShapeBorder? shape}) { + showDialog( context: context, barrierDismissible: false, builder: (context) { - return WillPopScope( onWillPop: () async { return false; }, child: AlertDialog( - content: widget, + + + shape: shape ?? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), ), + contentPadding: padding ?? const EdgeInsets.symmetric(vertical: 5), + content: widget + ) + ); }, ); } - - Future _check() async { String updateCheck = ''; try { final result = await methodChannel.invokeMethod( 'isUpdateAvailable', ); + if (result != null && result.isNotEmpty ) { + + return Platform.isIOS ? - if (result != null && result is String) { - return AppInfo.fromJson(json.decode(result)); + /// jsonData is getting with type Map so while parsing we are getting error of type mismatch + /// so right now now we are using jsonEncode and jsonDecode to solve this + + AppInfo.fromJson(json.decode(json.encode( result)) ): AppInfo.fromJson(json.decode(result) ); } - return AppInfo.fromJson((result ?? {}) as Map); + + return AppInfo.fromJson(( result ) ); } on PlatformException catch (e) { updateCheck = "Failed to check for update: '${e.message}'."; } @@ -132,5 +164,6 @@ class MethodChannelFlutterAppUpdatePackage print(AppInfo(updateData: UpdateData(updateCheck: updateCheck))); } return AppInfo(updateData: UpdateData(updateCheck: updateCheck)); + } } diff --git a/lib/flutter_app_update_package_platform_interface.dart b/lib/flutter_app_update_package_platform_interface.dart index 0f22ab9..63586c4 100644 --- a/lib/flutter_app_update_package_platform_interface.dart +++ b/lib/flutter_app_update_package_platform_interface.dart @@ -39,6 +39,9 @@ abstract class FlutterAppUpdatePackagePlatform extends PlatformInterface { required String appId, bool showNativeUI = true, Widget? Function(AppInfo)? customWidget, + EdgeInsetsGeometry? padding, + ShapeBorder? shape + }) { throw UnimplementedError('platformVersion() has not been implemented.'); diff --git a/lib/src/model/app_info_model.dart b/lib/src/model/app_info_model.dart index 01565c8..3dc6a6c 100644 --- a/lib/src/model/app_info_model.dart +++ b/lib/src/model/app_info_model.dart @@ -6,6 +6,7 @@ class AppInfo { bool? isMaintenance; UpdateData? updateData; MaintenanceData? maintenanceData; + AppInfo({ this.appName, @@ -17,6 +18,13 @@ class AppInfo { }); factory AppInfo.fromJson(Map json) => AppInfo( + + /// jsonData is getting with type Map so while parsing we are getting error of type mismatch + /// so right now now we are using jsonEncode and jsonDecode to solve this + + // final encodedJson = jsonEncode(jsonData); + // final json = jsonDecode(encodedJson); + appName: json["appName"], appLogo: json["appLogo"], id: json["id"], @@ -38,9 +46,9 @@ class AppInfo { class MaintenanceData { MaintenanceData(); - factory MaintenanceData.fromJson(Map json) => MaintenanceData(); + factory MaintenanceData.fromJson(Map json) => MaintenanceData(); - Map toJson() => {}; + Map toJson() => {}; } class UpdateData { @@ -97,4 +105,4 @@ class UpdateData { "isAndroidForcedUpdate": isAndroidForcedUpdate, "exception" : updateCheck, }; -} +} \ No newline at end of file