From f03a839027ea10664a7f05be843c48c659a3b7fa Mon Sep 17 00:00:00 2001 From: wangzhzh Date: Sun, 7 Apr 2024 11:20:11 +0800 Subject: [PATCH 1/6] Update README.md --- README.md | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 21405eb..995e070 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ -![logo](https://opensource.sensorsdata.cn/wp-content/uploads/logo.png) -

+ ## 神策简介 @@ -40,29 +39,16 @@ dependencies: * 5. 神策 SDK 研发团队会及时 review 代码,测试通过后合入。 -## 讨论 - -| 扫码加入神策数据开源社区 QQ 群
群号:785122381 | 扫码加入神策数据开源社区微信群 | -| ------ | ------ | -|![ QQ 讨论群](https://opensource.sensorsdata.cn/wp-content/uploads/ContentCommonPic_1.png) | ![ 微信讨论群 ](https://opensource.sensorsdata.cn/wp-content/uploads/ContentCommonPic_2.png) | - -## 公众号 - -| 扫码关注
神策数据开源社区 | 扫码关注
神策数据开源社区服务号 | -| ------ | ------ | -|![ 微信订阅号 ](https://opensource.sensorsdata.cn/wp-content/uploads/ContentCommonPic_3.png) | ![ 微信服务号 ](https://opensource.sensorsdata.cn/wp-content/uploads/ContentCommonPic_4.png) | - ## 新书推荐 -| 《数据驱动:从方法到实践》 | 《Android 全埋点解决方案》 | 《iOS 全埋点解决方案》 +| [《数据驱动:从方法到实践》](https://item.jd.com/12322322.html) | [《Android 全埋点解决方案》](https://item.jd.com/12574672.html) | [《iOS 全埋点解决方案》](https://item.jd.com/12867068.html) | ------ | ------ | ------ | -| [![《数据驱动:从方法到实践》](https://opensource.sensorsdata.cn/wp-content/uploads/data_driven_book_1.jpg)](https://item.jd.com/12322322.html) | [![《Android 全埋点解决方案》](https://opensource.sensorsdata.cn/wp-content/uploads/Android-全埋点thumbnail_1.png)](https://item.jd.com/12574672.html) | [![《iOS 全埋点解决方案》](https://opensource.sensorsdata.cn/wp-content/uploads/iOS-全埋点thumbnail_1.png)](https://item.jd.com/12867068.html) ## License -Copyright 2015-2022 Sensors Data Inc. +Copyright 2015-2024 Sensors Data Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -74,4 +60,4 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file +limitations under the License. From adc541e5259f81cdfa2825d62337571214322374 Mon Sep 17 00:00:00 2001 From: Exploring Date: Tue, 3 Sep 2024 15:06:48 +0800 Subject: [PATCH 2/6] Update LICENSE --- LICENSE | 229 +++++++------------------------------------------------- 1 file changed, 28 insertions(+), 201 deletions(-) diff --git a/LICENSE b/LICENSE index 7170a36..9990f57 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,28 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2015-2022 Sensors Data Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +软件名称:神策 ABTesting SDK +版本号:所有版本 +许可协议版本:1.0 + +1. 商业许可协议(用于商业用途需购买许可) +任何商业用途必须获得商业许可。 + +商业许可协议条款: + +- 商业用途:任何直接或间接产生收入的用途都需要购买商业许可。 +- 付款条款:在使用本软件用于商业用途之前,您必须支付全额许可费用。具体的付款方式将在双方联系后提供。 +- 商业支持:购买商业许可后,您将获得一年的技术支持和软件更新服务。 +- 禁止再许可:商业用户不得再许可、转售或转让本软件。每份商业许可仅适用于单一实体或公司。 +- 源代码访问:购买商业许可的用户将获得本软件的代码访问权限,并可根据业务需求进行内部修改。但不得公开发布或再分发修改后的版本。 +- 使用范围限制:商业许可仅限于购买者的内部使用,不得与第三方共享或用于为第三方提供服务。任何超出许可范围的使用行为均需额外授权,并可能产生额外费用。 +- 联系信息:如需购买商业许可,请联系 weizhangxiang@sensorsdata.com。 +- 知识产权声明:本软件的版权归神策网络科技(北京)有限公司所有。购买商业许可仅授予您使用权,所有权仍归属本公司。 +- 终止条款: 如果您未支付相关费用或违反本协议的任何条款,商业许可将自动终止。您必须立即停止所有商业用途,并销毁或删除所有软件副本。 + +2. 附加授权规则条款 +授权规则条款: + +- 功能限制:未经本软件作者的明确书面许可,您不得移除、绕过或规避本软件中的任何功能限制或试用限制。 +- 商标使用:未经授权,您不得在宣传、市场推广或销售产品时使用本软件的名称、商标或品牌标识。任何商标使用必须得到明确的书面许可。 +- 修改条款:本协议的条款可能会不时更新,用户有责任定期检查最新版本。任何重大更改将通过项目主页或电子邮件通知用户。 + +3. 联系方式 +如需更多信息或申请商业许可,请联系 weizhangxiang@sensorsdata.com。 From f3cf0e7f0141e3b26f8eb92b4bf775b62817653f Mon Sep 17 00:00:00 2001 From: Exploring Date: Tue, 3 Sep 2024 15:07:31 +0800 Subject: [PATCH 3/6] Update README.md --- README.md | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/README.md b/README.md index 995e070..10bb6c8 100644 --- a/README.md +++ b/README.md @@ -30,16 +30,6 @@ dependencies: 请参考神策官网 [Flutter 插件集成文档](https://manual.sensorsdata.cn/abtesting/latest/flutter-a-b-testing-50987212.html)。 -## 贡献 - -* 1. 在您的 GitHub 账户下 fork abtesting_sdk_flutter 开源项目; -* 2. 根据您的需求在本地 clone 一份 abtesting_sdk_flutter 源码; -* 3. 您修改或者新增功能后,push 到您 fork 的远程分支; -* 4. 创建 pull request,向 abtesting_sdk_flutter 官方开发分支提交合入请求; -* 5. 神策 SDK 研发团队会及时 review 代码,测试通过后合入。 - - - ## 新书推荐 | [《数据驱动:从方法到实践》](https://item.jd.com/12322322.html) | [《Android 全埋点解决方案》](https://item.jd.com/12574672.html) | [《iOS 全埋点解决方案》](https://item.jd.com/12867068.html) @@ -47,17 +37,4 @@ dependencies: ## License - -Copyright 2015-2024 Sensors Data Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +[License 协议](https://github.com/sensorsdata/abtesting_sdk_flutter/blob/main/LICENSE) From 2f51e6545a88b58612164277f993252713f23e89 Mon Sep 17 00:00:00 2001 From: Exploring Date: Wed, 4 Sep 2024 15:06:09 +0800 Subject: [PATCH 4/6] Update LICENSE --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 9990f57..1e2760d 100644 --- a/LICENSE +++ b/LICENSE @@ -13,7 +13,7 @@ - 禁止再许可:商业用户不得再许可、转售或转让本软件。每份商业许可仅适用于单一实体或公司。 - 源代码访问:购买商业许可的用户将获得本软件的代码访问权限,并可根据业务需求进行内部修改。但不得公开发布或再分发修改后的版本。 - 使用范围限制:商业许可仅限于购买者的内部使用,不得与第三方共享或用于为第三方提供服务。任何超出许可范围的使用行为均需额外授权,并可能产生额外费用。 -- 联系信息:如需购买商业许可,请联系 weizhangxiang@sensorsdata.com。 +- 联系信息:如需购买商业许可,请联系 dv@sensorsdata.com。 - 知识产权声明:本软件的版权归神策网络科技(北京)有限公司所有。购买商业许可仅授予您使用权,所有权仍归属本公司。 - 终止条款: 如果您未支付相关费用或违反本协议的任何条款,商业许可将自动终止。您必须立即停止所有商业用途,并销毁或删除所有软件副本。 @@ -25,4 +25,4 @@ - 修改条款:本协议的条款可能会不时更新,用户有责任定期检查最新版本。任何重大更改将通过项目主页或电子邮件通知用户。 3. 联系方式 -如需更多信息或申请商业许可,请联系 weizhangxiang@sensorsdata.com。 +如需更多信息或申请商业许可,请联系 dv@sensorsdata.com。 From f9e79d12176d67dc262598e9c85c9477e94425a3 Mon Sep 17 00:00:00 2001 From: Exploring Date: Fri, 18 Jul 2025 16:15:27 +0800 Subject: [PATCH 5/6] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 10bb6c8..3c82abb 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,15 @@ 神策 abtesting_sdk_flutter 插件,封装了神策 iOS & Android A/B Testing 常用 API ,使用此插件,可以使用 A/B Testing 功能。 +## 神策埋点 SDK 官网 +如需了解神策埋点 SDK 的更多商业授权信息,请访问[神策埋点 SDK 官网](https://jssdk.debugbox.sensorsdata.cn/)获取更多详细信息。 + +## 联系我们 +若您有商业合作或产品集成需求,请通过下面的渠道联系我们获取专业服务与支持。 + +| 加微信号:skycode008,或扫码添加联系人 | 扫码关注「神策埋点 SDK」公众号 ![gzh](https://github.com/sensorsdata/sa-sdk-android/blob/master/gzh.jpeg) | +| ------ | ------ | + ## 使用方式 在 Flutter 项目的 `pubspec.yaml` 文件中添加 `abtesting_sdk_flutter` 依赖 ```yml From 85658ed1361a20bf6987a68bec46e9a5b53772d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=80=E6=9C=88?= Date: Fri, 12 Dec 2025 15:34:30 +0800 Subject: [PATCH 6/6] Release 1.0.0 --- CHANGELOG.md | 16 +- README.md | 2 +- android/build.gradle | 8 + .../AbtestingSdkFlutterPlugin.java | 175 ++++++++++++++++-- example/android/app/build.gradle | 12 +- example/android/build.gradle | 4 +- example/android/gradle.properties | 11 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 37 ++-- example/ios/Runner.xcodeproj/project.pbxproj | 52 +++++- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/ios/Runner/AppDelegate.m | 37 +++- example/ios/Runner/Info.plist | 4 + example/lib/main.dart | 85 +++++++-- example/pubspec.lock | 100 ++++++++-- example/pubspec.yaml | 2 +- ios/Classes/AbtestingSdkFlutterPlugin.m | 89 ++++++++- ios/abtesting_sdk_flutter.podspec | 6 +- lib/abtesting_sdk_flutter.dart | 165 +++++++++++++++-- pubspec.lock | 81 ++++++-- pubspec.yaml | 4 +- 23 files changed, 744 insertions(+), 154 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30c6eb9..9877510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,20 @@ -## 0.0.1 +## 1.0.0 -* Flutter A/B Testing 插件,支持 Android 和 iOS 中使用 +* 单个接口和全局设置,支持自定义属性请求分流 +* 支持自定义主体请求分流 + +## 0.0.4 +* 修复 Android MethodChannel 偶现回调多次异常 + +## 0.0.3 + +* SDK 依赖由 jcenter 调整为 mavenCentral ## 0.0.2 * 新增初始化接口 -## 0.0.3 +## 0.0.1 -* SDK 依赖由 jcenter 调整为 mavenCentral +* Flutter A/B Testing 插件,支持 Android 和 iOS 中使用 diff --git a/README.md b/README.md index 3c82abb..9cd7986 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ ```yml dependencies: # 添加神策 flutter plugin - abtesting_sdk_flutter: ^0.0.3 + abtesting_sdk_flutter: ^1.0.0 ``` 执行 flutter packages get 命令安装插件 diff --git a/android/build.gradle b/android/build.gradle index 12a77ab..8590476 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -20,9 +20,17 @@ rootProject.allprojects { } apply plugin: 'com.android.library' +import com.android.Version +def agpVersion = Version.ANDROID_GRADLE_PLUGIN_VERSION +def needNamespace = agpVersion.tokenize('.')[0].toInteger() >= 7 && + agpVersion.tokenize('.')[1].toInteger() >= 3 +println ">>> Current AGP Version: $agpVersion" android { compileSdkVersion 30 + if (needNamespace) { + namespace 'com.sensorsdata.analytics.abtesting.abtesting_sdk_flutter' + } defaultConfig { minSdkVersion 16 diff --git a/android/src/main/java/com/sensorsdata/analytics/abtesting/abtesting_sdk_flutter/AbtestingSdkFlutterPlugin.java b/android/src/main/java/com/sensorsdata/analytics/abtesting/abtesting_sdk_flutter/AbtestingSdkFlutterPlugin.java index 25c94d7..075efe4 100644 --- a/android/src/main/java/com/sensorsdata/analytics/abtesting/abtesting_sdk_flutter/AbtestingSdkFlutterPlugin.java +++ b/android/src/main/java/com/sensorsdata/analytics/abtesting/abtesting_sdk_flutter/AbtestingSdkFlutterPlugin.java @@ -9,6 +9,7 @@ import com.sensorsdata.abtest.OnABTestReceivedData; import com.sensorsdata.abtest.SensorsABTest; import com.sensorsdata.abtest.SensorsABTestConfigOptions; +import com.sensorsdata.abtest.SensorsABTestExperiment; import org.json.JSONObject; @@ -27,9 +28,11 @@ /** AbtestingSdkFlutterPlugin */ public class AbtestingSdkFlutterPlugin implements FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android + /// The MethodChannel that will the communication between Flutter and native + /// Android /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// This local reference serves to register the plugin with the Flutter Engine + /// and unregister it /// when the Flutter Engine is detached from the Activity private MethodChannel channel; private Context applicationContext; @@ -61,6 +64,18 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { case "fastFetchABTest": fastFetchABTest(call, result); break; + case "setCustomIDs": + setCustomIDs(call, result); + break; + case "setCustomProperties": + setCustomProperties(call, result); + break; + case "fastFetchABTestWithExperiment": + fastFetchABTestWithExperiment(call, result); + break; + case "asyncFetchABTestWithExperiment": + asyncFetchABTestWithExperiment(call, result); + break; default: result.notImplemented(); break; @@ -68,8 +83,19 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { } private void startWithConfigOptions(MethodCall call, Result result) { - String url = (String) call.arguments; - SensorsABTestConfigOptions abTestConfigOptions = new SensorsABTestConfigOptions(url); + Map args = call.arguments(); + + String urlString = (String) args.get("urlString"); + Object customObj = args.get("customProperties"); + Map customProperties = null; + if (customObj instanceof Map) { + customProperties = (Map) customObj; + } + + SensorsABTestConfigOptions abTestConfigOptions = new SensorsABTestConfigOptions(urlString); + if (customProperties != null) { + abTestConfigOptions.setCustomProperties(assertMapToJSON(customProperties)); + } SensorsABTest.startWithConfigOptions(applicationContext, abTestConfigOptions); result.success(null); } @@ -127,26 +153,139 @@ public void run() { }); } - private void checkResult(Object abResult, Result result) { - if (abResult instanceof JSONObject) { - JSONObject jsonObject = (JSONObject) abResult; - result.success(jsonObject.toString()); + private void fastFetchABTestWithExperiment(MethodCall call, final Result result) { + List list = (List) call.arguments; + if (list == null || list.size() == 0) { + result.success(null); return; } - result.success(abResult); + + Map experimenMap = (Map) list.get(0); + // 构建最终的 Experiment + SensorsABTestExperiment experiment = buildExperimentFromMap(experimenMap); + + SensorsABTest.shareInstance().fastFetchABTest(experiment, new OnABTestReceivedData() { + @Override + public void onResult(final Object value) { + uiThreadHandler.post(new Runnable() { + @Override + public void run() { + checkResult(value, result); + } + }); + } + }); } - private static Map toMap(JSONObject obj) { - if (obj == null) { - return null; + private void asyncFetchABTestWithExperiment(MethodCall call, final Result result) { + List list = (List) call.arguments; + if (list == null || list.size() == 0) { + result.success(null); + return; } - Map data = new HashMap<>(); - Iterator it = obj.keys(); - while (it.hasNext()) { - String key = it.next(); - data.put(key, obj.opt(key)); + + Map experimenMap = (Map) list.get(0); + + // 构建最终的 Experiment + SensorsABTestExperiment experiment = buildExperimentFromMap(experimenMap); + + SensorsABTest.shareInstance().asyncFetchABTest(experiment, new OnABTestReceivedData() { + @Override + public void onResult(final Object value) { + uiThreadHandler.post(new Runnable() { + @Override + public void run() { + checkResult(value, result); + } + }); + } + }); + } + + private void setCustomIDs(MethodCall call, Result result) { + Map customIDs = call.arguments(); + SensorsABTest.shareInstance().setCustomIDs(customIDs); + result.success(null); + } + + private void setCustomProperties(MethodCall call, Result result) { + Map customProperties = call.arguments(); + SensorsABTest.shareInstance().setCustomProperties(assertMapToJSON(customProperties)); + result.success(null); + } + + // 从 Map 构建 Experiment 对象 + private SensorsABTestExperiment buildExperimentFromMap(Map experimentDic) { + String paramName = (String) experimentDic.get("paramName"); + Object defaultValue = experimentDic.get("defaultValue"); + if (defaultValue instanceof Map) { + defaultValue = new JSONObject((Map) defaultValue); + } + + SensorsABTestExperiment.ExperimentBuilder builder = SensorsABTestExperiment.newBuilder(paramName, + defaultValue); + Object timeoutObj = experimentDic.get("timeoutInterval"); + if (timeoutObj instanceof Number) { + int timeout = ((Number) timeoutObj).intValue(); // 毫秒 + builder.setTimeoutMillSeconds(timeout); + } + + Map properties = (Map) experimentDic.get("properties"); + if (properties != null) { + for (String key : properties.keySet()) { + Object value = properties.get(key); + + if (value == null) { + builder.addProperty(key, (String) null); + } else if (value instanceof String) { + builder.addProperty(key, (String) value); + } else if (value instanceof Boolean) { + builder.addProperty(key, (Boolean) value); + } else if (value instanceof Integer) { + builder.addProperty(key, (Integer) value); + } else if (value instanceof Long) { + builder.addProperty(key, (Long) value); + } else if (value instanceof Double) { + builder.addProperty(key, (Double) value); + } else if (value instanceof Float) { + builder.addProperty(key, ((Float) value).doubleValue()); + } else if (value instanceof List) { + try { + builder.addProperty(key, (List) value); + } catch (Exception e) { + } + } else { + // 兜底处理,直接转字符串 + builder.addProperty(key, String.valueOf(value)); + } + } + } + + // 构建最终的 Experiment + SensorsABTestExperiment experiment = builder.create(); + return experiment; + } + + private void checkResult(Object abResult, Result result) { + try { + if (abResult instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) abResult; + result.success(jsonObject.toString()); + return; + } + result.success(abResult); + } catch (java.lang.Exception e) { + // ignored + } + } + + private static JSONObject assertMapToJSON(Map map) { + if (map != null) { + return new JSONObject(map); + } else { + // SALog.d(TAG, "传入的属性为空"); + return null; } - return data; } @Override diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index c13becf..476e08d 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -26,9 +26,9 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 34 ndkVersion flutter.ndkVersion - + namespace 'com.sensorsdata.analytics.abtesting.example' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -47,7 +47,7 @@ android { applicationId "com.sensorsdata.analytics.abtesting.example" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion flutter.minSdkVersion + minSdkVersion 21 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -55,9 +55,9 @@ android { buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + minifyEnabled false // 禁用 R8 + shrinkResources false // 可选 + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } diff --git a/example/android/build.gradle b/example/android/build.gradle index 83ae220..029d299 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:8.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 94adc3a..7c0de0c 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,10 @@ -org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true -android.enableJetifier=true +# 必须开启,否则构建性能差 +org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 + +# 使用 Android Studio 内置 JDK(Java 21) +org.gradle.java.home=/Applications/Android\ Studio.app/Contents/jbr/Contents/Home + +# 减少构建错误的推荐配置 +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index cb24abd..d5ce57c 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip \ No newline at end of file diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9367d48..7c56964 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index f7d6a5e..0656068 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' + platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 38382ed..5e765bf 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,23 +1,16 @@ PODS: - - abtesting_sdk_flutter (0.0.1): + - abtesting_sdk_flutter (1.0.0): - Flutter - SensorsABTesting - Flutter (1.0.0) - - sensors_analytics_flutter_plugin (2.0.0): + - sensors_analytics_flutter_plugin (4.2.1): - Flutter - - SensorsAnalyticsSDK (>= 2.1.17) - - SensorsABTesting (0.1.0): - - SensorsAnalyticsSDK (>= 3.0.0) - - SensorsAnalyticsSDK (3.0.2): - - SensorsAnalyticsSDK/Core (= 3.0.2) - - SensorsAnalyticsSDK/AutoTrack (3.0.2): - - SensorsAnalyticsSDK/Common - - SensorsAnalyticsSDK/Common (3.0.2) - - SensorsAnalyticsSDK/Core (3.0.2): - - SensorsAnalyticsSDK/Common - - SensorsAnalyticsSDK/Visualized - - SensorsAnalyticsSDK/Visualized (3.0.2): - - SensorsAnalyticsSDK/AutoTrack + - SensorsAnalyticsSDK (>= 4.9.0) + - SensorsABTesting (1.1.0): + - SensorsAnalyticsSDK (>= 4.5.6) + - SensorsAnalyticsSDK (5.0.5): + - SensorsAnalyticsSDK/Core (= 5.0.5) + - SensorsAnalyticsSDK/Core (5.0.5) DEPENDENCIES: - abtesting_sdk_flutter (from `.symlinks/plugins/abtesting_sdk_flutter/ios`) @@ -38,12 +31,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sensors_analytics_flutter_plugin/ios" SPEC CHECKSUMS: - abtesting_sdk_flutter: e43611513a82c30a8b3e070d3a535b944c354d2d - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c - sensors_analytics_flutter_plugin: 09080518746dbc455182989f2839245f8c9c1fd9 - SensorsABTesting: f441708cde9dd0a764e0c1b3d59bbbeb79a89d8e - SensorsAnalyticsSDK: 0d3ec178f0405eeea64d8a9aafc9b99b8ac812f9 + abtesting_sdk_flutter: e7f445e6b79b41ffb0a4e6aab4098ad4f7f3b702 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + sensors_analytics_flutter_plugin: 15a81c8bfdbfa44b1f8cdb51dc8fe0427980f3c5 + SensorsABTesting: 9d512e44b6426242fe85b26307c755de4edb393f + SensorsAnalyticsSDK: 9b7967f54889d2c0aa734a10caffc0e5a617773a -PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d +PODFILE CHECKSUM: fb408a289158b00660fcdf4ee69e15b9fae7e08f -COCOAPODS: 1.10.2 +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index a2e7060..a0c4a17 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -150,6 +150,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, DAE13CFB62FE20364467C1D4 /* [CP] Copy Pods Resources */, + 2250FBA2C25D67DEE4D1ACEC /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -166,7 +167,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -207,12 +208,31 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2250FBA2C25D67DEE4D1ACEC /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -223,6 +243,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -350,7 +371,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -364,15 +385,20 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 75FYWDWHL5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.sensorsdata.analytics.abtesting.abtestingSdkFlutterExample; + PRODUCT_BUNDLE_IDENTIFIER = cn.sensorsdata.abtesting.Example; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Dev_All_Sensorsdata; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -424,7 +450,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -473,7 +499,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -487,15 +513,20 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 75FYWDWHL5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.sensorsdata.analytics.abtesting.abtestingSdkFlutterExample; + PRODUCT_BUNDLE_IDENTIFIER = cn.sensorsdata.abtesting.Example; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Dev_All_Sensorsdata; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -505,15 +536,20 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 75FYWDWHL5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.sensorsdata.analytics.abtesting.abtestingSdkFlutterExample; + PRODUCT_BUNDLE_IDENTIFIER = cn.sensorsdata.abtesting.Example; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Dev_All_Sensorsdata; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..e67b280 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ +#if __has_include() +#import +#else +#import "SensorsAnalyticsSDK.h" +#endif + #import @implementation AppDelegate @@ -9,14 +14,19 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [GeneratedPluginRegistrant registerWithRegistry:self]; // Override point for customization after application launch. + +// [self startSensorsAnalyticsSDKWithOptions: launchOptions]; +// +// [self startSensorsABTest]; + +// - // 测试环境,获取试验地址 - NSString* kSABResultsTestURL = @"http://abtesting.saas.debugbox.sensorsdata.cn/api/v2/abtest/online/results?project-key=438B9364C98D54371751BA82F6484A1A03A5155E"; + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} +- (void)startSensorsAnalyticsSDKWithOptions:(NSDictionary *)launchOptions { // 测试环境,数据接收地址 NSString* kSABTestServerURL = @"http://10.130.6.4:8106/sa?project=default"; - - SAConfigOptions *options = [[SAConfigOptions alloc] initWithServerURL:kSABTestServerURL launchOptions:launchOptions]; // options.autoTrackEventType = SensorsAnalyticsEventTypeAppStart | SensorsAnalyticsEventTypeAppEnd | SensorsAnalyticsEventTypeAppClick | SensorsAnalyticsEventTypeAppViewScreen; @@ -27,16 +37,23 @@ - (BOOL)application:(UIApplication *)application options.enableVisualizedAutoTrack = YES; options.enableJavaScriptBridge = YES; options.enableLog = YES; + options.flushNetworkPolicy = SensorsAnalyticsNetworkTypeALL; [SensorsAnalyticsSDK startWithConfigOptions:options]; + + [SensorsAnalyticsSDK.sharedInstance track:@"test_track" withProperties:@{@"device_type": @"iPhone"}]; + // [[SensorsAnalyticsSDK sharedInstance] setFlushNetworkPolicy:SensorsAnalyticsNetworkTypeALL]; -// - //SensorsABTestConfigOptions *abtestConfigOptions = [[SensorsABTestConfigOptions alloc] initWithURL:kSABResultsTestURL]; - //[SensorsABTest startWithConfigOptions:abtestConfigOptions]; -// +} + +- (void)startSensorsABTest { + // 测试环境,获取试验地址 + NSString* kSABResultsTestURL = @"http://abtesting.saas.debugbox.sensorsdata.cn/api/v2/abtest/online/results?project-key=438B9364C98D54371751BA82F6484A1A03A5155E"; + + SensorsABTestConfigOptions *abtestConfigOptions = [[SensorsABTestConfigOptions alloc] initWithURL:kSABResultsTestURL]; + [SensorsABTest startWithConfigOptions:abtestConfigOptions]; - return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 1dd1d13..9594ccd 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -27,6 +29,8 @@ NSAllowsArbitraryLoads + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/example/lib/main.dart b/example/lib/main.dart index b9da8d5..8ea080d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -18,8 +18,7 @@ class _MyAppState extends State { void initState() { super.initState(); SensorsAnalyticsFlutterPlugin.init( - serverUrl: - "http://10.130.6.4:8106/sa?project=default", + serverUrl: "http://10.130.6.4:8106/sa?project=default", autoTrackTypes: { SAAutoTrackType.APP_START, SAAutoTrackType.APP_VIEW_SCREEN, @@ -37,16 +36,20 @@ class _MyAppState extends State { flushBulkSize: 150, enableLog: true, javaScriptBridge: true, - encrypt: true, - heatMap: true, - visualized: VisualizedConfig(autoTrack: true, properties: true), + // encrypt: true, + // heatMap: true, + // visualized: VisualizedConfig(autoTrack: true, properties: true), android: AndroidConfig( maxCacheSize: 48 * 1024 * 1024, jellybean: true, subProcessFlush: true), ios: IOSConfig(maxCacheSize: 10000), - globalProperties: {'aaa': 'aaa-value', 'bbb': 'bbb-value'}); + globalProperties: {'aaa': 'flutter 全局属性', 'bbb': 'flutter 全局属性-2'}); +// 注册公共属性 + // final anonymousId = SensorsAnalyticsFlutterPlugin.getAnonymousId(); + // SensorsAnalyticsFlutterPlugin.registerSuperProperties( + // {'anonymous_id': anonymousId}); } @override @@ -61,21 +64,39 @@ class _MyAppState extends State { Text('Running on: $_platformVersion\n'), TextButton( onPressed: () async { - int? result = await SensorsABTest.fetchCacheABTest("int", 666); + // 旧测试分流地址:http://abtesting.saas.debugbox.sensorsdata.cn/api/v2/abtest/online/results?project-key=438B9364C98D54371751BA82F6484A1A03A5155E + SensorsABTest.startWithConfigOptions( + "http://abtesting.saas.debugbox.sensorsdata.cn/api/v2/abtest/online/results?project-key=438B9364C98D54371751BA82F6484A1A03A5155E"); + print("startWithConfigOptions====="); + }, + child: Text("startWithConfigOptions")), + TextButton( + onPressed: () async { + var map = {"address": "beijing"}; + SensorsABTest.startWithConfigOptions( + "http://abtesting.saas.debugbox.sensorsdata.cn/api/v2/abtest/online/results?project-key=438B9364C98D54371751BA82F6484A1A03A5155E", + map); + print("startWithConfigOptions customProperties====="); + }, + child: Text("startWithConfigOptions-customProperties")), + TextButton( + onPressed: () async { + int? result = + await SensorsABTest.fetchCacheABTest("index_cqs", 666); print("fetchCacheABTest result is===$result"); }, child: Text("fetchCacheABTest")), TextButton( onPressed: () async { Map? result = - await SensorsABTest.fetchABTest("int", {}, 3111); - print("fetchABTest result is===$result"); + await SensorsABTest.asyncFetchABTest("json_cqs", {}); + print("asyncFetchABTest result is===$result"); }, - child: Text("fetchABTest")), + child: Text("asyncFetchABTest")), TextButton( onPressed: () async { - String? result = - await SensorsABTest.fastFetchABTest("param_cat", "should_be_a_cat"); + String? result = await SensorsABTest.fastFetchABTest( + "color_cqs", "字符串默认值"); print("fastFetchABTest result is===$result"); }, child: Text("fastFetchABTest")), @@ -87,10 +108,44 @@ class _MyAppState extends State { child: Text("login")), TextButton( onPressed: () async { - SensorsABTest.startWithConfigOptions("http://abtesting.saas.debugbox.sensorsdata.cn/api/v2/abtest/online/results?project-key=438B9364C98D54371751BA82F6484A1A03A5155E"); - print("startWithConfigOptions====="); + var experiment = + SensorsABTestExperiment.experimentWithParamName( + "cqs_city1", "cqs_city 默认值"); + // 设置自定义属性 + experiment.properties = {"\$city": "合肥"}; + experiment.timeoutInterval = 20 * 1000; + SensorsABTest.fastFetchABTestWithExperiment(experiment) + .then((value) { + print("fastFetchABTestWithExperiment result is===$value"); + }); }, - child: Text("startWithConfigOptions")), + child: Text("fastFetchABTestWithExperiment")), + TextButton( + onPressed: () async { + // 不设置自定义属性 + var experiment = + SensorsABTestExperiment.experimentWithParamName( + "cqs_city", "cqs_city 默认值"); + experiment.timeoutInterval = 20 * 1000; + SensorsABTest.asyncFetchABTestWithExperiment(experiment) + .then((value) { + print("asyncFetchABTestWithExperiment result is===$value"); + }); + }, + child: Text("asyncFetchABTestWithExperiment")), + TextButton( + onPressed: () async { + // 设置自定义主体 + SensorsABTest.setCustomIDs({"custom_id_1": "custom_value_1"}); + }, + child: Text("setCustomIDs")), + TextButton( + onPressed: () async { + // 设置自定义主体 + SensorsABTest.setCustomProperties( + {"custom_properties_1": "custom_value_1"}); + }, + child: Text("setCustomProperties")), ])), ), ); diff --git a/example/pubspec.lock b/example/pubspec.lock index 676d549..256b612 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,32 +7,36 @@ packages: path: ".." relative: true source: path - version: "0.0.3" + version: "1.0.0" async: dependency: transitive description: name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.flutter-io.cn" source: hosted - version: "2.9.0" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" @@ -40,13 +44,15 @@ packages: dependency: transitive description: name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.flutter-io.cn" source: hosted - version: "1.16.0" + version: "1.18.0" cupertino_icons: dependency: "direct main" description: name: cupertino_icons + sha256: "486b7bc707424572cdf7bd7e812a0c146de3fd47ecadf070254cc60383f21dd8" url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" @@ -54,6 +60,7 @@ packages: dependency: transitive description: name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" @@ -67,41 +74,83 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.7" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" matcher: dependency: transitive description: name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.12" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.5" + version: "0.8.0" meta: dependency: transitive description: name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.0" + version: "1.12.0" path: dependency: transitive description: name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.2" + version: "1.9.0" sensors_analytics_flutter_plugin: dependency: "direct main" description: name: sensors_analytics_flutter_plugin + sha256: "56b82c2532546cf2c4445f4f29d342f543637de3aa11e0a8a5b59e746eadc7b2" url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.1" + version: "4.2.1" sky_engine: dependency: transitive description: flutter @@ -111,34 +160,39 @@ packages: dependency: transitive description: name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.flutter-io.cn" source: hosted - version: "1.9.0" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.flutter-io.cn" source: hosted - version: "1.10.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.1" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" @@ -146,16 +200,26 @@ packages: dependency: transitive description: name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.flutter-io.cn" source: hosted - version: "0.4.12" + version: "0.7.0" vector_math: dependency: transitive description: name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.2" + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.flutter-io.cn" + source: hosted + version: "14.2.1" sdks: - dart: ">=2.17.0-0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3e510f5..3b24061 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - sensors_analytics_flutter_plugin: ^2.2.1 + sensors_analytics_flutter_plugin: ^4.2.1 abtesting_sdk_flutter: # When depending on this package from a real application you should use: # abtesting_sdk_flutter: ^x.y.z diff --git a/ios/Classes/AbtestingSdkFlutterPlugin.m b/ios/Classes/AbtestingSdkFlutterPlugin.m index c1ce60f..f85f08d 100644 --- a/ios/Classes/AbtestingSdkFlutterPlugin.m +++ b/ios/Classes/AbtestingSdkFlutterPlugin.m @@ -19,19 +19,33 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [self fastFetchABTest:call result:result]; } else if ([@"startWithConfigOptions" isEqualToString:call.method]) { [self startWithConfigOptions:call result:result]; + } else if ([@"setCustomIDs" isEqualToString:call.method]) { + [self setCustomIDs:call result:result]; + } else if ([@"setCustomProperties" isEqualToString:call.method]) { + [self setCustomProperties:call result:result]; + } else if ([@"fastFetchABTestWithExperiment" isEqualToString:call.method]) { + [self fastFetchABTestWithExperiment:call result:result]; + } else if ([@"asyncFetchABTestWithExperiment" isEqualToString:call.method]) { + [self asyncFetchABTestWithExperiment:call result:result]; } else { result(FlutterMethodNotImplemented); } } --(void)startWithConfigOptions:(FlutterMethodCall*)call result:(FlutterResult)result{ - NSString* url = (NSString*)call.arguments; - SensorsABTestConfigOptions *abtestConfigOptions = [[SensorsABTestConfigOptions alloc] initWithURL:url]; +- (void)startWithConfigOptions:(FlutterMethodCall*)call result:(FlutterResult)result{ + NSDictionary *args = call.arguments; + NSString *urlString = args[@"urlString"]; + NSDictionary *customProperties = args[@"customProperties"]; + + SensorsABTestConfigOptions *abtestConfigOptions = [[SensorsABTestConfigOptions alloc] initWithURL:urlString]; + if (customProperties != nil && [customProperties isKindOfClass:[NSDictionary class]]) { + abtestConfigOptions.customProperties = customProperties; + } [SensorsABTest startWithConfigOptions:abtestConfigOptions]; result(nil); } --(void)fetchCacheABTest:(FlutterMethodCall*)call result:(FlutterResult)result{ +- (void)fetchCacheABTest:(FlutterMethodCall*)call result:(FlutterResult)result{ NSArray* arguments = (NSArray *)call.arguments; if (arguments.count < 2) { result(nil); @@ -46,7 +60,7 @@ -(void)fetchCacheABTest:(FlutterMethodCall*)call result:(FlutterResult)result{ result(finalresult); } --(void)asyncFetchABTest:(FlutterMethodCall*)call result:(FlutterResult)flutterResult { +- (void)asyncFetchABTest:(FlutterMethodCall*)call result:(FlutterResult)flutterResult { NSArray* arguments = (NSArray *)call.arguments; if (arguments.count < 3) { flutterResult(nil); @@ -64,17 +78,58 @@ -(void)asyncFetchABTest:(FlutterMethodCall*)call result:(FlutterResult)flutterRe }]; } --(void)fastFetchABTest:(FlutterMethodCall*)call result:(FlutterResult)flutterResult { +- (void)fastFetchABTest:(FlutterMethodCall*)call result:(FlutterResult)flutterResult { NSArray* arguments = (NSArray *)call.arguments; - if (arguments.count < 3) { + NSString* paramName = arguments[0]; + double second = [arguments[2] doubleValue] / 1000; + + [[SensorsABTest sharedInstance] fastFetchABTestWithParamName:paramName defaultValue:arguments[1] timeoutInterval:second completionHandler:^(id _Nullable finalresult) { + if([finalresult isKindOfClass:[NSDictionary class]]) { + NSDictionary* dic = finalresult; + finalresult = [self convertToJsonData:dic]; + } + flutterResult(finalresult); + }]; +} + +- (void)fastFetchABTestWithExperiment:(FlutterMethodCall*)call result:(FlutterResult)flutterResult { + NSArray* arguments = (NSArray *)call.arguments; + if (arguments.count == 0) { flutterResult(nil); return; } - NSString* paramName = arguments[0]; - double second = [arguments[2] doubleValue] / 1000; + NSDictionary *experimentDic = arguments[0]; + SensorsABTestExperiment *experiment = [SensorsABTestExperiment experimentWithParamName:experimentDic[@"paramName"] defaultValue:experimentDic[@"defaultValue"]]; + if (experimentDic[@"timeoutInterval"]) { + experiment.timeoutInterval = [experimentDic[@"timeoutInterval"] doubleValue] / 1000; + } + experiment.properties = experimentDic[@"properties"]; - [[SensorsABTest sharedInstance] fastFetchABTestWithParamName:paramName defaultValue:arguments[1] timeoutInterval:second completionHandler:^(id _Nullable finalresult) { + [[SensorsABTest sharedInstance] fastFetchABTestWithExperiment:experiment completionHandler:^(id _Nullable finalresult) { + if([finalresult isKindOfClass:[NSDictionary class]]) { + NSDictionary* dic = finalresult; + finalresult = [self convertToJsonData:dic]; + } + flutterResult(finalresult); + }]; +} + +- (void)asyncFetchABTestWithExperiment:(FlutterMethodCall*)call result:(FlutterResult)flutterResult { + NSArray* arguments = (NSArray *)call.arguments; + if (arguments.count == 0) { + flutterResult(nil); + return; + } + + NSDictionary *experimentDic = arguments[0]; + SensorsABTestExperiment *experiment = [SensorsABTestExperiment experimentWithParamName:experimentDic[@"paramName"] defaultValue:experimentDic[@"defaultValue"]]; + if (experimentDic[@"timeoutInterval"]) { + experiment.timeoutInterval = [experimentDic[@"timeoutInterval"] doubleValue] / 1000; + } + experiment.properties = experimentDic[@"properties"]; + + [[SensorsABTest sharedInstance] asyncFetchABTestWithExperiment:experiment completionHandler:^(id _Nullable finalresult) { if([finalresult isKindOfClass:[NSDictionary class]]) { NSDictionary* dic = finalresult; finalresult = [self convertToJsonData:dic]; @@ -83,6 +138,20 @@ -(void)fastFetchABTest:(FlutterMethodCall*)call result:(FlutterResult)flutterRes }]; } + +- (void)setCustomIDs:(FlutterMethodCall*)call result:(FlutterResult)result { + NSDictionary *customIDs = call.arguments; + [[SensorsABTest sharedInstance] setCustomIDs:customIDs]; + result(nil); +} + +- (void)setCustomProperties:(FlutterMethodCall*)call result:(FlutterResult)result { + NSDictionary *customProperties = call.arguments; + [[SensorsABTest sharedInstance] setCustomProperties:customProperties]; + result(nil); +} + +/// NSDictionary 转 JSON 字符串 - (NSString *)convertToJsonData:(NSDictionary *)dict { if (![NSJSONSerialization isValidJSONObject:dict]) { NSLog(@"obj is not valid JSON: %@",dict); diff --git a/ios/abtesting_sdk_flutter.podspec b/ios/abtesting_sdk_flutter.podspec index fff82de..1eea9b5 100644 --- a/ios/abtesting_sdk_flutter.podspec +++ b/ios/abtesting_sdk_flutter.podspec @@ -4,19 +4,19 @@ # Pod::Spec.new do |s| s.name = 'abtesting_sdk_flutter' - s.version = '0.0.3' + s.version = '1.0.0' s.summary = 'A/B Testing Flutter Plugin' s.description = <<-DESC A/B Testing Flutter Plugin DESC s.homepage = 'http://github.com/sensorsdata/abtesting_sdk_flutter' s.license = { :file => '../LICENSE' } - s.author = { 'SensorsData' => 'zhangwei@sensorsdata.cn' } + s.author = { 'SensorsData' => 'chuqiangsheng@sensorsdata.cn' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.platform = :ios, '8.0' + s.platform = :ios, '9.0' s.dependency 'SensorsABTesting' # Flutter.framework does not contain a i386 slice. diff --git a/lib/abtesting_sdk_flutter.dart b/lib/abtesting_sdk_flutter.dart index 972a0f8..a14161a 100644 --- a/lib/abtesting_sdk_flutter.dart +++ b/lib/abtesting_sdk_flutter.dart @@ -3,6 +3,69 @@ import 'dart:convert'; import 'package:flutter/services.dart'; +/// A/B Testing 试验参数配置 +class SensorsABTestExperiment { + /// --- 只读字段 --- + final String paramName; + final dynamic defaultValue; + + /// --- 可读写字段 --- + Map? properties; + double timeoutInterval; + + /// 默认超时时间(Flutter 中,毫秒单位) + static const double defaultTimeoutInterval = 30 * 1000; + + /// 禁止使用默认构造方法 + SensorsABTestExperiment._({ + required this.paramName, + required this.defaultValue, + this.properties, + double? timeoutInterval, + }) : timeoutInterval = timeoutInterval ?? defaultTimeoutInterval; + + /// 指定构造方法 —— 对应 initWithParamName:defaultValue: + factory SensorsABTestExperiment({ + required String paramName, + required dynamic defaultValue, + Map? properties, + double? timeoutInterval, + }) { + return SensorsABTestExperiment._( + paramName: paramName, + defaultValue: defaultValue, + properties: properties, + timeoutInterval: timeoutInterval, + ); + } + + /// 工厂方法 —— 对应 +experimentWithParamName:defaultValue: + factory SensorsABTestExperiment.experimentWithParamName( + String paramName, + dynamic defaultValue, + ) { + return SensorsABTestExperiment._( + paramName: paramName, + defaultValue: defaultValue, + ); + } + + /// 转成 Map + Map toMap() { + final map = { + "paramName": paramName, + "defaultValue": defaultValue, + "timeoutInterval": timeoutInterval, + }; + + if (properties != null) { + map["properties"] = properties; + } + + return map; + } +} + class SensorsABTest { static const MethodChannel _channel = const MethodChannel('sa_abtesting_sdk'); static const int TIMEOUT_REQUEST = 30 * 1000; @@ -10,9 +73,17 @@ class SensorsABTest { ///初始化 A/B Testing,在此之前请确保先初始化神策分析 SDK: ///[Android 参考文档](https://manual.sensorsdata.cn/sa/latest/tech_sdk_client_android_basic-17563982.html), ///[iOS 参考文档](https://manual.sensorsdata.cn/sa/latest/tech_sdk_client_ios_use-27724338.html)。 - ///[url] 为服务器地址 - static void startWithConfigOptions(String url){ - _channel.invokeMethod("startWithConfigOptions", url); + ///[urlString] 分流地址 url + ///[customProperties] 自定义属性,可选 + static void startWithConfigOptions( + String urlString, [ + Map? customProperties, + ]) { + final params = { + "urlString": urlString, + "customProperties": customProperties, + }; + _channel.invokeMethod("startWithConfigOptions", params); } /// 从缓存中获取试验结果。[paramName] 是参数名称,[defaultValue] 是对应参数的默认值, @@ -28,31 +99,101 @@ class SensorsABTest { return _channel.invokeMethod("fetchCacheABTest", [paramName, defaultValue]); } + /// 如果本地有缓存,则返回缓存数据;否则从网络请求最新的试验数据,默认 30s 超时时间。[paramName] 是参数名称,[defaultValue] 是对应参数的默认值, + /// 泛型 [T] 的支持类型包括:[int]、[String]、[bool] 和代表 JSON 的 [Map] 类型, + /// [timeoutMillSeconds] 的值默认是 30 * 1000,单位是 ms。 + static Future fastFetchABTest(String paramName, T defaultValue, + [int timeoutMillSeconds = TIMEOUT_REQUEST]) async { + if (defaultValue is Map) { + dynamic result = await _channel.invokeMethod( + "fastFetchABTest", [paramName, defaultValue, timeoutMillSeconds]); + dynamic finalResult = jsonDecode(result); + return Future.value(finalResult); + } + return _channel.invokeMethod( + "fastFetchABTest", [paramName, defaultValue, timeoutMillSeconds]); + } + /// 始终从网络请求试验结果,默认 30s 超时时间。[paramName] 是参数名称,[defaultValue] 是对应参数的默认值, /// 泛型 [T] 的支持类型包括:[int]、[String]、[bool] 和代表 JSON 的 [Map] 类型, /// [timeoutMillSeconds] 的值默认是 30 * 1000,单位是 ms。 - static Future fetchABTest(String paramName, T defaultValue, + static Future asyncFetchABTest(String paramName, T defaultValue, [int timeoutMillSeconds = TIMEOUT_REQUEST]) async { if (defaultValue is Map) { - dynamic result = await _channel - .invokeMethod("asyncFetchABTest", [paramName, defaultValue, timeoutMillSeconds]); + dynamic result = await _channel.invokeMethod( + "asyncFetchABTest", [paramName, defaultValue, timeoutMillSeconds]); dynamic finalResult = jsonDecode(result); return Future.value(finalResult); } - return _channel.invokeMethod("asyncFetchABTest", [paramName, defaultValue, timeoutMillSeconds]); + return _channel.invokeMethod( + "asyncFetchABTest", [paramName, defaultValue, timeoutMillSeconds]); } - /// 如果本地有缓存,则返回缓存数据;否则从网络请求最新的试验数据,默认 30s 超时时间。[paramName] 是参数名称,[defaultValue] 是对应参数的默认值, + /// 如果本地有缓存,则返回缓存数据;否则从网络请求最新的试验数据,默认 30s 超时时间。 + /// [experiment] 是试验参数配置对象, + /// 泛型 [T] 的支持类型包括:[int]、[String]、[bool] 和代表 JSON 的 [Map] 类型。 + static Future fastFetchABTestWithExperiment( + SensorsABTestExperiment experiment) async { + dynamic map = experiment.toMap(); + + if (experiment.defaultValue is Map) { + dynamic result = + await _channel.invokeMethod("fastFetchABTestWithExperiment", [map]); + dynamic finalResult = jsonDecode(result); + return Future.value(finalResult); + } + return _channel.invokeMethod("fastFetchABTestWithExperiment", [map]); + } + + /// 始终从网络请求试验结果,默认 30s 超时时间。 + /// [experiment] 是试验参数配置对象, + /// 泛型 [T] 的支持类型包括:[int]、[String]、[bool] 和代表 JSON 的 [Map] 类型。 + static Future asyncFetchABTestWithExperiment( + SensorsABTestExperiment experiment) async { + dynamic map = experiment.toMap(); + + if (experiment.defaultValue is Map) { + dynamic result = + await _channel.invokeMethod("asyncFetchABTestWithExperiment", [map]); + dynamic finalResult = jsonDecode(result); + return Future.value(finalResult); + } + return _channel.invokeMethod("asyncFetchABTestWithExperiment", [map]); + } + +/** + * 设置获取试验时的自定义主体 ID,全局有效 + * [customIDs] 自定义主体 ID + */ + static void setCustomIDs(Map customIDs) { + _channel.invokeMethod("setCustomIDs", customIDs); + } + + /** + * 设置自定义属性 + * 设置多次时,以最后设置为准,会直接覆盖前次设置内容 + * 设置自定义属性,下次 SDK 初始化后重置 + * [customProperties] 设置的自定义属性内容 + */ + static void setCustomProperties(Map customProperties) { + _channel.invokeMethod("setCustomProperties", customProperties); + } + + /// --- 已废弃方法,请使用 asyncFetchABTest --- + /// 始终从网络请求试验结果,默认 30s 超时时间。[paramName] 是参数名称,[defaultValue] 是对应参数的默认值, /// 泛型 [T] 的支持类型包括:[int]、[String]、[bool] 和代表 JSON 的 [Map] 类型, /// [timeoutMillSeconds] 的值默认是 30 * 1000,单位是 ms。 - static Future fastFetchABTest(String paramName, T defaultValue, + @Deprecated( + 'Please use the asyncFetchABTest interface, It has the same functionality.') + static Future fetchABTest(String paramName, T defaultValue, [int timeoutMillSeconds = TIMEOUT_REQUEST]) async { if (defaultValue is Map) { - dynamic result = await _channel - .invokeMethod("fastFetchABTest", [paramName, defaultValue, timeoutMillSeconds]); + dynamic result = await _channel.invokeMethod( + "asyncFetchABTest", [paramName, defaultValue, timeoutMillSeconds]); dynamic finalResult = jsonDecode(result); return Future.value(finalResult); } - return _channel.invokeMethod("fastFetchABTest", [paramName, defaultValue, timeoutMillSeconds]); + return _channel.invokeMethod( + "asyncFetchABTest", [paramName, defaultValue, timeoutMillSeconds]); } } diff --git a/pubspec.lock b/pubspec.lock index 44e6d01..5128f32 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,27 +5,31 @@ packages: dependency: transitive description: name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.flutter-io.cn" source: hosted - version: "2.9.0" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" @@ -33,13 +37,15 @@ packages: dependency: transitive description: name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.flutter-io.cn" source: hosted - version: "1.16.0" + version: "1.18.0" fake_async: dependency: transitive description: name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" @@ -53,34 +59,62 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" matcher: dependency: transitive description: name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.12" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.5" + version: "0.8.0" meta: dependency: transitive description: name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.0" + version: "1.12.0" path: dependency: transitive description: name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.2" + version: "1.9.0" sky_engine: dependency: transitive description: flutter @@ -90,34 +124,39 @@ packages: dependency: transitive description: name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.flutter-io.cn" source: hosted - version: "1.9.0" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.flutter-io.cn" source: hosted - version: "1.10.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.1" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" @@ -125,16 +164,26 @@ packages: dependency: transitive description: name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.flutter-io.cn" source: hosted - version: "0.4.12" + version: "0.7.0" vector_math: dependency: transitive description: name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.2" + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.flutter-io.cn" + source: hosted + version: "14.2.1" sdks: - dart: ">=2.17.0-0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index bc14717..37f7f9a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: abtesting_sdk_flutter description: A/B Testing Flutter Plugin -version: 0.0.3 +version: 1.0.0 homepage: http://github.com/sensorsdata/abtesting_sdk_flutter environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.12.0 <4.0.0" flutter: ">=1.20.0" dependencies: