diff --git a/.pubignore b/.pubignore new file mode 100644 index 00000000..c21f9e6e --- /dev/null +++ b/.pubignore @@ -0,0 +1 @@ +example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 39c0dbe9..099aef8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Flutter Plugin Changelog +## Version 10.3.0 - May 1, 2025 +Minor release that updates the Android SDK to 19.6.2 and the iOS SDK to 19.3.1 and fixes an Embedded View bug. + +### Changes +- Updated Android SDK to [19.6.2](https://github.com/urbanairship/android-library/releases/tag/19.6.2) +- Updated iOS SDK to [19.3.1](https://github.com/urbanairship/ios-library/releases/tag/19.3.1) +- Added support for JSON attributes +- Added new method `Airship.channel.waitForChannelId()` that waits for the channel ID to be created +- Fixed bug in `Airship.inApp.isEmbeddedAvailableStream` that disrupted gated rendering of Embedded Views + ## Version 10.2.0 - March 27, 2025 Minor release that updates the Android SDK to 19.4.0 and the iOS SDK to 19.1.1 diff --git a/android/build.gradle b/android/build.gradle index e125aa67..d29de101 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,7 +5,7 @@ buildscript { ext.kotlin_version = '1.9.0' ext.coroutine_version = '1.5.2' ext.datastore_preferences_version = '1.1.1' - ext.airship_framework_proxy_version = '13.3.0' + ext.airship_framework_proxy_version = '14.2.1' repositories { diff --git a/android/src/main/kotlin/com/airship/flutter/AirshipPlugin.kt b/android/src/main/kotlin/com/airship/flutter/AirshipPlugin.kt index 6bc730f1..656ada93 100644 --- a/android/src/main/kotlin/com/airship/flutter/AirshipPlugin.kt +++ b/android/src/main/kotlin/com/airship/flutter/AirshipPlugin.kt @@ -119,6 +119,7 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { // Channel "channel#getChannelId" -> result.resolve(scope, call) { proxy.channel.getChannelId() } + "channel#waitForChannelId" -> result.resolve(scope, call) { proxy.channel.waitForChannelId() } "channel#addTags" -> result.resolve(scope, call) { call.stringList().forEach { proxy.channel.addTag(it) diff --git a/android/src/main/kotlin/com/airship/flutter/AirshipPluginVersion.kt b/android/src/main/kotlin/com/airship/flutter/AirshipPluginVersion.kt index 1d3a46dd..e4d77469 100644 --- a/android/src/main/kotlin/com/airship/flutter/AirshipPluginVersion.kt +++ b/android/src/main/kotlin/com/airship/flutter/AirshipPluginVersion.kt @@ -2,6 +2,6 @@ package com.airship.flutter class AirshipPluginVersion { companion object { - const val AIRSHIP_PLUGIN_VERSION = "10.2.0" + const val AIRSHIP_PLUGIN_VERSION = "10.3.0" } } diff --git a/example/.pubignore b/example/.pubignore new file mode 100644 index 00000000..c21f9e6e --- /dev/null +++ b/example/.pubignore @@ -0,0 +1 @@ +example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved \ No newline at end of file diff --git a/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift new file mode 100644 index 00000000..8e8af102 --- /dev/null +++ b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Generated file. Do not edit. +// + +import PackageDescription + +let package = Package( + name: "FlutterGeneratedPluginSwiftPackage", + platforms: [ + .iOS("16.1") + ], + products: [ + .library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"]) + ], + dependencies: [ + .package(name: "airship_flutter", path: "/Users/david.crow/source/airship-flutter/ios/airship_flutter") + ], + targets: [ + .target( + name: "FlutterGeneratedPluginSwiftPackage", + dependencies: [ + .product(name: "airship-flutter", package: "airship_flutter") + ] + ) + ] +) diff --git a/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift new file mode 100644 index 00000000..62e7b11a --- /dev/null +++ b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift @@ -0,0 +1,3 @@ +// +// Generated file. Do not edit. +// diff --git a/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0d4616a4..65a3a259 100644 --- a/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/urbanairship/airship-mobile-framework-proxy.git", "state" : { - "revision" : "5cdd2519a10e32708da38e05c9945c233f1bf9cf", - "version" : "13.0.1" + "revision" : "e4b880d8a9ed3f3227f23db123344fcbb132009e", + "version" : "14.2.1" } }, { @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/urbanairship/ios-library.git", "state" : { - "revision" : "adb01f7a7abf2998379bf64b5ca3fbbdcfb90ef7", - "version" : "19.0.3" + "revision" : "c6d97692d11e842793614db10a7d78a3b6b9c2c8", + "version" : "19.3.1" } } ], diff --git a/ios/.pubignore b/ios/.pubignore new file mode 100644 index 00000000..c21f9e6e --- /dev/null +++ b/ios/.pubignore @@ -0,0 +1 @@ +example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved \ No newline at end of file diff --git a/ios/airship_flutter.podspec b/ios/airship_flutter.podspec index d255e4d3..d4088736 100644 --- a/ios/airship_flutter.podspec +++ b/ios/airship_flutter.podspec @@ -1,5 +1,5 @@ -AIRSHIP_FLUTTER_VERSION="10.2.0" +AIRSHIP_FLUTTER_VERSION="10.3.0" # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html @@ -19,7 +19,7 @@ Airship flutter plugin. s.source_files = 'airship_flutter/Sources/airship_flutter/**/*' s.dependency 'Flutter' s.ios.deployment_target = "15.0" - s.dependency "AirshipFrameworkProxy", "13.3.0" + s.dependency "AirshipFrameworkProxy", "14.2.1" s.swift_version = "5.0.0" s.resource_bundles = {'airship_flutter_privacy' => ['airship_flutter/Sources/airship_flutter/PrivacyInfo.xcprivacy']} end diff --git a/ios/airship_flutter/Package.resolved b/ios/airship_flutter/Package.resolved index aa72c949..cf66a787 100644 --- a/ios/airship_flutter/Package.resolved +++ b/ios/airship_flutter/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/urbanairship/airship-mobile-framework-proxy.git", "state" : { - "revision" : "e1f350572f97b9493fbecb9cee3ff9c6c924d683", - "version" : "13.0.0" + "revision" : "e4b880d8a9ed3f3227f23db123344fcbb132009e", + "version" : "14.2.1" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/urbanairship/ios-library.git", "state" : { - "revision" : "adb01f7a7abf2998379bf64b5ca3fbbdcfb90ef7", - "version" : "19.0.3" + "revision" : "c6d97692d11e842793614db10a7d78a3b6b9c2c8", + "version" : "19.3.1" } } ], diff --git a/ios/airship_flutter/Package.swift b/ios/airship_flutter/Package.swift index efab142a..a8979ce7 100644 --- a/ios/airship_flutter/Package.swift +++ b/ios/airship_flutter/Package.swift @@ -10,7 +10,7 @@ let package = Package( .library( name: "airship-flutter", targets: ["airship_flutter"]) ], dependencies: [ - .package(url: "https://github.com/urbanairship/airship-mobile-framework-proxy.git", from: "13.0.0") + .package(url: "https://github.com/urbanairship/airship-mobile-framework-proxy.git", from: "14.2.1") ], targets: [ .target( diff --git a/ios/airship_flutter/Sources/airship_flutter/AirshipPlugin.swift b/ios/airship_flutter/Sources/airship_flutter/AirshipPlugin.swift index 181a493f..68bd23c6 100644 --- a/ios/airship_flutter/Sources/airship_flutter/AirshipPlugin.swift +++ b/ios/airship_flutter/Sources/airship_flutter/AirshipPlugin.swift @@ -117,6 +117,9 @@ public class AirshipPlugin: NSObject, FlutterPlugin { case "channel#getChannelId": return try AirshipProxy.shared.channel.channelID + case "channel#waitForChannelId": + return try await AirshipProxy.shared.channel.waitForChannelID() + case "channel#addTags": try AirshipProxy.shared.channel.addTags( try call.requireStringArrayArg() diff --git a/ios/airship_flutter/Sources/airship_flutter/AirshipPluginVersion.swift b/ios/airship_flutter/Sources/airship_flutter/AirshipPluginVersion.swift index 4a4c24b4..f2fe586f 100644 --- a/ios/airship_flutter/Sources/airship_flutter/AirshipPluginVersion.swift +++ b/ios/airship_flutter/Sources/airship_flutter/AirshipPluginVersion.swift @@ -1,5 +1,5 @@ import Foundation class AirshipPluginVersion { - static let pluginVersion = "10.2.0" + static let pluginVersion = "10.3.0" } diff --git a/lib/src/airship_channel.dart b/lib/src/airship_channel.dart index 590d0406..8a03c605 100644 --- a/lib/src/airship_channel.dart +++ b/lib/src/airship_channel.dart @@ -15,6 +15,14 @@ class AirshipChannel { return await _module.channel.invokeMethod('channel#getChannelId'); } + /// Returns the channel ID. If the channel ID is not yet created the function it will wait for it before returning. After + /// the channel ID is created, this method functions the same as `identifier`. + /// + /// @returns A future with the channel ID. + Future waitForChannelId() async { + return await _module.channel.invokeMethod('channel#waitForChannelId'); + } + /// Creates a [SubscriptionListEditor] to modify the subscription lists associated with the channel. SubscriptionListEditor editSubscriptionLists() { return SubscriptionListEditor("channel#editSubscriptionLists", _module.channel); diff --git a/lib/src/airship_embedded_view.dart b/lib/src/airship_embedded_view.dart index 7c0ac220..0d877a96 100644 --- a/lib/src/airship_embedded_view.dart +++ b/lib/src/airship_embedded_view.dart @@ -41,18 +41,24 @@ class AirshipEmbeddedViewState extends State with AutomaticKeepAliveClientMixin { late MethodChannel _channel; late Stream _readyStream; + late final StreamSubscription _readySubscription; + bool? _isEmbeddedAvailable; @override void initState() { super.initState(); + + // Get seed value once + _isEmbeddedAvailable = + Airship.inApp.isEmbeddedAvailable(embeddedId: widget.embeddedId); + + // Then listen for changes _readyStream = Airship.inApp.isEmbeddedAvailableStream(embeddedId: widget.embeddedId); - _readyStream.listen((isEmbeddedAvailable) { - if (mounted) { - setState(() { - _isEmbeddedAvailable = isEmbeddedAvailable; - }); + _readySubscription = _readyStream.listen((v) { + if (mounted && v != _isEmbeddedAvailable) { + setState(() => _isEmbeddedAvailable = v); } }); } @@ -161,6 +167,7 @@ class AirshipEmbeddedViewState extends State @override void dispose() { + _readySubscription.cancel(); _channel.setMethodCallHandler(null); super.dispose(); } diff --git a/lib/src/attribute_editor.dart b/lib/src/attribute_editor.dart index 1bdca1df..0019ac1f 100644 --- a/lib/src/attribute_editor.dart +++ b/lib/src/attribute_editor.dart @@ -9,6 +9,8 @@ class AttributeEditor { static const ATTRIBUTE_OPERATION_SET = "set"; static const ATTRIBUTE_OPERATION_VALUE = "value"; static const ATTRIBUTE_OPERATION_VALUE_TYPE = "type"; + static const ATTRIBUTE_OPERATION_INSTANCE_ID = "instance_id"; + static const ATTRIBUTE_OPERATION_EXPIRATION_MS = "expiration_milliseconds"; final MethodChannel channel; @@ -18,8 +20,7 @@ class AttributeEditor { /// The attribute operation list. final List> operations; - AttributeEditor(this.type, this.channel) - : operations = []; + AttributeEditor(this.type, this.channel) : operations = []; /// Removes an attribute. void removeAttribute(String name) { @@ -59,6 +60,43 @@ class AttributeEditor { }); } + /// Adds a JSON attribute. + /// + /// @param name The attribute name. + /// @param instanceId The instance ID. + /// @param json The json value. + /// @param expiration Optional expiration date. + void setJsonAttribute( + String name, String instanceId, Map json, + [DateTime? expiration]) { + final operation = { + ATTRIBUTE_OPERATION_TYPE: ATTRIBUTE_OPERATION_SET, + ATTRIBUTE_OPERATION_KEY: name, + ATTRIBUTE_OPERATION_INSTANCE_ID: instanceId, + ATTRIBUTE_OPERATION_VALUE: json, + ATTRIBUTE_OPERATION_VALUE_TYPE: "json" + }; + + if (expiration != null) { + operation[ATTRIBUTE_OPERATION_EXPIRATION_MS] = + expiration.millisecondsSinceEpoch; + } + + operations.add(operation); + } + + /// Removes a JSON attribute. + /// + /// @param name The attribute name. + /// @param instanceId The instance ID. + void removeJsonAttribute(String name, String instanceId) { + operations.add({ + ATTRIBUTE_OPERATION_TYPE: ATTRIBUTE_OPERATION_REMOVE, + ATTRIBUTE_OPERATION_KEY: name, + ATTRIBUTE_OPERATION_INSTANCE_ID: instanceId + }); + } + /// Applies the attribute operations. Future apply() async { return await channel.invokeMethod(type, operations); diff --git a/pubspec.yaml b/pubspec.yaml index ffdc8eb2..a8faede9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: airship_flutter description: "Cross-platform plugin interface for the native Airship iOS and Android SDKs. Simplifies adding Airship to Flutter apps." -version: 10.2.0 +version: 10.3.0 homepage: https://www.airship.com/ repository: https://github.com/urbanairship/airship-flutter issue_tracker: https://github.com/urbanairship/airship-flutter/issues