Skip to content

Commit 449bc7f

Browse files
Merge pull request #77 from qonversion/release/3.1.0
Release/3.1.0
2 parents 14ad2d8 + f9e9bb0 commit 449bc7f

File tree

15 files changed

+236
-53
lines changed

15 files changed

+236
-53
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.1.0
2+
* Add support for promo purchases on iOS
3+
* Add Identity support
4+
15
## 3.0.1
26
* Fix QOfferings mapping error
37
* Internal logic optimization for Android

android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
group 'com.qonversion.flutter.sdk.qonversion_flutter_sdk'
2-
version '3.0.1'
2+
version '3.1.0'
33

44
buildscript {
55
ext.kotlin_version = '1.3.50'
6-
ext.qonversion_version = '2.7.6'
6+
ext.qonversion_version = '2.8.0'
77
repositories {
88
google()
99
jcenter()

android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/BaseListenerWrapper.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@ package com.qonversion.flutter.sdk.qonversion_flutter_sdk
33
import io.flutter.plugin.common.BinaryMessenger
44
import io.flutter.plugin.common.EventChannel
55

6-
abstract class BaseListenerWrapper(
7-
val binaryMessenger: BinaryMessenger
6+
class BaseListenerWrapper internal constructor(
7+
private val binaryMessenger: BinaryMessenger,
8+
private val eventChannelPostfix: String
89
) {
910

10-
protected abstract val eventChannelPostfix: String
11-
12-
protected var eventChannel: EventChannel? = null
13-
protected var eventStreamHandler: BaseEventStreamHandler? = null
11+
private var eventChannel: EventChannel? = null
12+
var eventStreamHandler: BaseEventStreamHandler? = null
1413

1514
fun register() {
1615
eventStreamHandler =

android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/QonversionFlutterSdkPlugin.kt

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ import com.qonversion.android.sdk.dto.offerings.QOfferings
1919
import com.qonversion.android.sdk.dto.products.QProduct
2020

2121
/** QonversionFlutterSdkPlugin */
22-
class QonversionFlutterSdkPlugin internal constructor(registrar: Registrar): MethodCallHandler, BaseListenerWrapper(registrar.messenger()) {
22+
class QonversionFlutterSdkPlugin internal constructor(registrar: Registrar): MethodCallHandler {
2323
private val activity: Activity = registrar.activity()
2424
private val application: Application = activity.application
25-
26-
override val eventChannelPostfix = "updated_purchases"
25+
private var deferredPurchasesStreamHandler: BaseEventStreamHandler? = null
2726

2827
companion object {
2928
@JvmStatic
@@ -32,8 +31,14 @@ class QonversionFlutterSdkPlugin internal constructor(registrar: Registrar): Met
3231
val instance = QonversionFlutterSdkPlugin(registrar)
3332
channel.setMethodCallHandler(instance)
3433

35-
// Register Updated Purchases Event Channel
36-
instance.register()
34+
// Register deferred purchases events
35+
val purchasesListener = BaseListenerWrapper(registrar.messenger(), "updated_purchases")
36+
purchasesListener.register()
37+
instance.deferredPurchasesStreamHandler = purchasesListener.eventStreamHandler
38+
39+
// Register promo purchases events. Android SDK does not generate any promo purchases yet
40+
val promoPurchasesListener = BaseListenerWrapper(registrar.messenger(), "promo_purchases")
41+
promoPurchasesListener.register()
3742
}
3843
}
3944

@@ -61,6 +66,14 @@ class QonversionFlutterSdkPlugin internal constructor(registrar: Registrar): Met
6166
"offerings" -> {
6267
return offerings(result)
6368
}
69+
"logout" -> {
70+
Qonversion.logout()
71+
return result.success(null)
72+
}
73+
"resetUser" -> {
74+
Qonversion.resetUser()
75+
return result.success(null)
76+
}
6477
}
6578

6679
// Methods with args
@@ -77,6 +90,7 @@ class QonversionFlutterSdkPlugin internal constructor(registrar: Registrar): Met
7790
"addAttributionData" -> addAttributionData(args, result)
7891
"checkTrialIntroEligibility" -> checkTrialIntroEligibility(args, result)
7992
"storeSdkInfo" -> storeSdkInfo(args, result)
93+
"identify" -> identify(args["userId"] as? String, result)
8094
else -> result.notImplemented()
8195
}
8296
}
@@ -103,12 +117,22 @@ class QonversionFlutterSdkPlugin internal constructor(registrar: Registrar): Met
103117
startListeningPendingPurchasesEvents()
104118
}
105119

120+
private fun identify(userId: String?, result: Result) {
121+
if (userId == null) {
122+
result.noUserIdError()
123+
return
124+
}
125+
126+
Qonversion.identify(userId)
127+
result.success(null)
128+
}
129+
106130
private fun startListeningPendingPurchasesEvents() {
107131
Qonversion.setUpdatedPurchasesListener(object : UpdatedPurchasesListener {
108132
override fun onPermissionsUpdate(permissions: Map<String, QPermission>) {
109133
val payload = Gson().toJson(permissions.mapValues { it.value.toMap() })
110134

111-
eventStreamHandler?.eventSink?.success(payload)
135+
deferredPurchasesStreamHandler?.eventSink?.success(payload)
112136
}
113137
})
114138
}

example/lib/home.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,15 @@ class _HomeViewState extends State<HomeView> {
102102
}
103103

104104
Future<void> initPlatformState() async {
105+
if (kDebugMode) {
106+
await Qonversion.setDebugMode();
107+
}
108+
105109
_qLaunchResult = await Qonversion.launch(
106110
'PV77YHL7qnGvsdmpTs7gimsxUvY-Znl2',
107111
isObserveMode: false,
108112
);
109113

110-
if (kDebugMode) {
111-
await Qonversion.setDebugMode();
112-
}
113-
114114
setState(() {});
115115
}
116116

example/lib/products_view.dart

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,42 @@ class ProductsView extends StatefulWidget {
1111
class _ProductsViewState extends State<ProductsView> {
1212
var _products = <String, QProduct>{};
1313
QOfferings _offerings;
14-
StreamSubscription<Map<String, QPermission>> _subscription;
14+
StreamSubscription<Map<String, QPermission>> _deferredPurchasesStream;
15+
StreamSubscription<String> _promoPurchasesStream;
1516

1617
@override
1718
void initState() {
1819
super.initState();
1920
_loadProducts();
2021
_loadOfferings();
2122

22-
_subscription =
23+
_deferredPurchasesStream =
2324
Qonversion.updatedPurchasesStream.listen((event) => print(event));
25+
26+
_promoPurchasesStream =
27+
Qonversion.promoPurchasesStream.listen((promoPurchaseId) async {
28+
try {
29+
final permissions = await Qonversion.promoPurchase(promoPurchaseId);
30+
// Get Qonversion product by App Store ID
31+
final qProduct = _products.values.firstWhere(
32+
(element) => element.storeId == promoPurchaseId,
33+
orElse: () => null);
34+
// Get permission by Qonversion product
35+
final permission = permissions.values.firstWhere(
36+
(element) => element.productId == qProduct?.qonversionId,
37+
orElse: () => null);
38+
39+
print(permission?.isActive);
40+
} catch (e) {
41+
print(e);
42+
}
43+
});
2444
}
2545

2646
@override
2747
void dispose() {
28-
_subscription.cancel();
48+
_deferredPurchasesStream.cancel();
49+
_promoPurchasesStream.cancel();
2950

3051
super.dispose();
3152
}
@@ -108,8 +129,12 @@ class _ProductsViewState extends State<ProductsView> {
108129
color: Colors.blue,
109130
textColor: Colors.white,
110131
onPressed: () async {
111-
final res = await Qonversion.purchase(product.qonversionId);
112-
print(res[product.qonversionId]?.isActive);
132+
final permissions = await Qonversion.purchase(product.qonversionId);
133+
final permission = permissions.values.firstWhere(
134+
(element) => element.productId == product.qonversionId,
135+
orElse: () => null);
136+
137+
print(permission?.isActive);
113138
},
114139
),
115140
),

ios/Classes/FlutterError+Custom.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,10 @@ extension FlutterError {
7777
static let noSdkInfo = FlutterError(code: "15",
7878
message: "Could not find sdk info",
7979
details: passValidValue)
80+
81+
static func promoPurchaseError(_ productId: String) -> FlutterError {
82+
return FlutterError (code: "PromoPurchase",
83+
message: "Could not find completion block for Product ID: \(productId)",
84+
details: passValidValue)
85+
}
8086
}

ios/Classes/SwiftQonversionFlutterSdkPlugin.swift

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import Flutter
77
import Qonversion
88

99
public class SwiftQonversionFlutterSdkPlugin: NSObject, FlutterPlugin {
10-
var purchasesEventStreamHandler: BaseEventStreamHandler?
10+
var deferredPurchasesStreamHandler: BaseEventStreamHandler?
11+
var promoPurchasesStreamHandler: BaseEventStreamHandler?
12+
var promoPurchasesExecutionBlocks = [String: Qonversion.PromoPurchaseCompletionHandler]()
1113

1214
public static func register(with registrar: FlutterPluginRegistrar) {
1315
let messenger: FlutterBinaryMessenger
@@ -20,12 +22,15 @@ public class SwiftQonversionFlutterSdkPlugin: NSObject, FlutterPlugin {
2022
let instance = SwiftQonversionFlutterSdkPlugin()
2123
registrar.addMethodCallDelegate(instance, channel: channel)
2224

23-
// Register events listeners
25+
// Register deferred purchases events
2426
let purchasesListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: "updated_purchases")
25-
purchasesListener.register() { instance.purchasesEventStreamHandler = $0 }
26-
27-
// Setting delegate as soon as plugin is registered
27+
purchasesListener.register() { instance.deferredPurchasesStreamHandler = $0 }
2828
Qonversion.setPurchasesDelegate(instance)
29+
30+
// Register promo purchases events
31+
let promoPurchasesListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: "promo_purchases")
32+
promoPurchasesListener.register() { instance.promoPurchasesStreamHandler = $0 }
33+
Qonversion.setPromoPurchasesDelegate(instance)
2934
}
3035

3136
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@@ -52,7 +57,15 @@ public class SwiftQonversionFlutterSdkPlugin: NSObject, FlutterPlugin {
5257

5358
case "offerings":
5459
return offerings(result)
55-
60+
61+
case "logout":
62+
Qonversion.logout()
63+
return result(nil)
64+
65+
case "resetUser":
66+
Qonversion.resetUser()
67+
return result(nil)
68+
5669
default:
5770
break
5871
}
@@ -70,6 +83,9 @@ public class SwiftQonversionFlutterSdkPlugin: NSObject, FlutterPlugin {
7083
case "purchase":
7184
return purchase(args["productId"] as? String, result)
7285

86+
case "promoPurchase":
87+
return promoPurchase(args["productId"] as? String, result)
88+
7389
case "setUserId":
7490
return setUserId(args["userId"] as? String, result)
7591

@@ -87,6 +103,9 @@ public class SwiftQonversionFlutterSdkPlugin: NSObject, FlutterPlugin {
87103

88104
case "storeSdkInfo":
89105
return storeSdkInfo(args, result)
106+
107+
case "identify":
108+
return identify(args["userId"] as? String, result)
90109

91110
default:
92111
return result(FlutterMethodNotImplemented)
@@ -107,7 +126,17 @@ public class SwiftQonversionFlutterSdkPlugin: NSObject, FlutterPlugin {
107126
result(resultMap)
108127
}
109128
}
110-
129+
130+
private func identify(_ userId: String?, _ result: @escaping FlutterResult) {
131+
guard let userId = userId else {
132+
result(FlutterError.noUserId)
133+
return
134+
}
135+
136+
Qonversion.identify(userId)
137+
result(nil)
138+
}
139+
111140
private func products(_ result: @escaping FlutterResult) {
112141
Qonversion.products { (products, error) in
113142
if let error = error {
@@ -133,6 +162,25 @@ public class SwiftQonversionFlutterSdkPlugin: NSObject, FlutterPlugin {
133162
}
134163
}
135164

165+
private func promoPurchase(_ productId: String?, _ result: @escaping FlutterResult) {
166+
guard let productId = productId else {
167+
return result(FlutterError.noProductId)
168+
}
169+
170+
if let executionBlock = promoPurchasesExecutionBlocks[productId] {
171+
promoPurchasesExecutionBlocks.removeValue(forKey: productId)
172+
173+
executionBlock { (permissions, error, isCancelled) in
174+
let purchaseResult = PurchaseResult(permissions: permissions,
175+
error: error,
176+
isCancelled: isCancelled)
177+
result(purchaseResult.toMap())
178+
}
179+
} else {
180+
result(FlutterError.promoPurchaseError(productId))
181+
}
182+
}
183+
136184
private func checkPermissions(_ result: @escaping FlutterResult) {
137185
Qonversion.checkPermissions { (permissions, error) in
138186
if let error = error {
@@ -274,6 +322,14 @@ extension SwiftQonversionFlutterSdkPlugin: Qonversion.PurchasesDelegate {
274322
public func qonversionDidReceiveUpdatedPermissions(_ permissions: [String : Qonversion.Permission]) {
275323
let payload = permissions.mapValues { $0.toMap() }.toJson()
276324

277-
purchasesEventStreamHandler?.eventSink?(payload)
325+
deferredPurchasesStreamHandler?.eventSink?(payload)
326+
}
327+
}
328+
329+
extension SwiftQonversionFlutterSdkPlugin: QNPromoPurchasesDelegate {
330+
public func shouldPurchasePromoProduct(withIdentifier productID: String, executionBlock: @escaping Qonversion.PromoPurchaseCompletionHandler) {
331+
promoPurchasesExecutionBlocks[productID] = executionBlock
332+
333+
promoPurchasesStreamHandler?.eventSink?(productID)
278334
}
279335
}

ios/qonversion_flutter.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
Pod::Spec.new do |s|
66
s.name = 'qonversion_flutter'
7-
s.version = '3.0.1'
7+
s.version = '3.1.0'
88
s.summary = 'Flutter Qonversion SDK'
99
s.description = <<-DESC
1010
Powerful yet simple subscription analytics
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
1616
s.source_files = 'Classes/**/*'
1717
s.dependency 'Flutter'
1818
s.platform = :ios, '9.0'
19-
s.dependency 'Qonversion', '2.13.3'
19+
s.dependency 'Qonversion', '2.13.4'
2020

2121
# Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.
2222
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }

lib/src/constants.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class Constants {
2020
static const mLaunch = 'launch';
2121
static const mProducts = 'products';
2222
static const mPurchase = 'purchase';
23+
static const mPromoPurchase = 'promoPurchase';
2324
static const mUpdatePurchase = 'updatePurchase';
2425
static const mCheckPermissions = 'checkPermissions';
2526
static const mRestore = 'restore';
@@ -33,6 +34,9 @@ class Constants {
3334
static const mOfferings = 'offerings';
3435
static const mCheckTrialIntroEligibility = 'checkTrialIntroEligibility';
3536
static const mStoreSdkInfo = 'storeSdkInfo';
37+
static const mIdentify = 'identify';
38+
static const mResetUser = 'resetUser';
39+
static const mLogout = 'logout';
3640

3741
// Keys for NSUserDefaults on iOS and SharedPreferences on Android
3842
static const keyPrefix = 'com.qonversion.keys';

0 commit comments

Comments
 (0)