nnsdk 使用说明
nnsdk 是一个用于简化应用初始化、用户归因、内购支付和埋点上报的 Flutter 插件,封装了 iOS 原生能力,对外提供统一的 Dart API。
支持环境
- Flutter 版本:推荐
3.19.6及以上 - iOS:
- 最低支持
iOS 15.0+ - 需要开启 In-App Purchase 能力,正确配置 Bundle Id、签名证书等
- 最低支持
1. 引用与初始化
1.1 导入头文件 / 包
Dart 侧:
import 'package:nnsdk_with_ad/nnsdk.dart';
import 'package:nnsdk_with_ad/Entity/ConfigEntity.dart';
import 'package:nnsdk_with_ad/Entity/BridgingEntity.dart';
import 'package:nnsdk_with_ad/Entity/InitResultEntity.dart';
如果你在原生侧iOS SDK 提供的接口,请根据对应平台在工程中引入生成的插件代码(通常 Flutter 工程会自动处理,一般无需手动导入)。
1.2 Flutter侧初始化配置与桥接参数
ConfigEntity 用于配置基础参数,BridgingEntity 用于配置服务端接口地址等桥接信息。示例:
final config = ConfigEntity(
isDebug: true, // 是否调试模式
isLog: true, // 是否打印日志
host: 'https://api.example.com', // 隔离域名
encryptedHeader: true, // 是否加密请求头
prdId: 'your_product_id',
gdtAppId: 'your_gdt_app_id', // 广点通 APP ID
gdtAppKey: 'your_gdt_app_key', // 广点通 APP KEY
);
final bridging = BridgingEntity(
userLogin: '/user/login',
addOrder: '/order/add',
verifyOrderV2: '/order/verify',
reportState: '/order/reportState',
goodsList: '/goods/list',
appStartNew: '/app/start',
recoverOrder: '/order/recover',
updateFields: '/user/updateDigitalUnionId',
// 其余字段根据你的后端实际定义填写
);
1.3 Flutter侧初始化 SDK
建议在应用启动后尽早初始化,例如在 main() 或首页 initState 中:
Future<void> initNnSdk() async {
final result = await Nnsdk.instance.initSdk(
config: config,
bridging: bridging,
);
if (result.success == true) {
// 初始化成功,可以安全调用支付、埋点等接口
print('nnsdk 初始化成功');
} else if (result.errorCode == -1) {
// 网络问题,初始化失败 (未授权网络/未知网络)
print('nnsdk 初始化失败,未授权网络/未知网络');
}else {
// 初始化失败,可根据 errorCode / errorMsg 做相应处理或提示
print('nnsdk 初始化失败: code=${result.errorCode}, msg=${result.errorMsg}');
}
}
1.4 原生AppDelegate初始化
import UIKit
import Flutter
import nnsdk
@main
@objc class AppDelegate: FlutterAppDelegate {
let nnsdk = NnsdkPlugin()
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
/// 1.2.7版本新增 广点通融合归因处理,需要验证广点通后台日志,查看接入情况
nnsdk.onOpenUrl(url: url)
return true
}
override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
/// 1.2.7版本新增 广点通融合归因处理,需要验证广点通后台日志,查看接入情况
nnsdk.onContinueUserActivity(userActivity: userActivity)
return true;
}
}
2. 获取基础信息
final sdk = Nnsdk.instance;
final deviceId = sdk.deviceId; // 设备唯一标识
final deviceInfo = sdk.deviceInfo; // 设备信息实体 DeviceInfoEntity
final attribution = sdk.attributionEntity; // 归因信息
final isNewUser = sdk.isNewUser; // 是否新用户
final userId = sdk.userId; // 中台用户 ID
3. 商品与支付
3.1 获取商品列表
在 initSdk 成功后,SDK 会自动拉取商品并与原生商店信息合并,可通过:
final products = sdk.productList; // List<CommodityItemEntity>
3.2 发起下单与支付
Future<void> startPurchase(String commodityId) async {
final result = await Nnsdk.instance.createOrder(
commodityId: commodityId,
transferParameter: 'optional_extra_param', // 商品透传参数 jsonString,由商品接口下发
userIdentifier: 'user_123', // 可选,不传则使用 deviceId
);
if (result.status) {
// 下单 + 支付 + 验单成功
print('支付成功,订单号: ${result.orderId}');
} else {
// 支付或验单失败,可结合 code / message 做处理
print('支付失败: code=${result.code}, msg=${result.message}');
}
}
3.3 恢复购买
Future<void> restorePurchase() async {
final restoreResult = await Nnsdk.instance.restore('user_123'); // 可选 userIdentifier
if (restoreResult.status == 1) {
print('恢复购买成功');
} else {
print('恢复购买失败,status=${restoreResult.status}');
}
}
3.4 处理未完成订单
Future<void> handleUnfinishedOrders() async {
// isDiscard = false: 继续验单,失败再 finish;true: 直接 finish 掉
final ok = await Nnsdk.instance.finishUnVerifyOrders(false, 'user_123');
print('处理未完成订单结果: $ok');
}
4. 埋点上报
Future<void> trackExample() async {
await Nnsdk.instance.track(
eventName: 'app_open',
params: {
'scene': 'home',
'from': 'push',
},
);
}
5. 苹果商品价格与优惠展示示例
nnsdk 在拉取商品后,会将服务端商品信息与苹果商店的价格、订阅促销信息合并到 CommodityItemEntity / CommodityStoreEntity 中,你可以基于这些字段来展示原价、促销价、首期优惠等信息。
5.1 展示基础价格
import 'package:nnsdk_with_ad/Entity/CommodityItemEntity.dart';
void showBasePrice(CommodityItemEntity item) {
final store = item.storeEntity; // CommodityStoreEntity?,根据你的实体结构获取
if (store == null) return;
final currency = store.storeCurrency ?? 'CNY';
final price = store.storePrice ?? 0;
print('原价:$price $currency');
}
5.2 展示订阅首期免费试用(freeTrial)
CommodityStoreEntity 中已经提供了 isFreeIntroTrial、introductoryRoundValue、introductoryRoundUnitText() 等辅助方法,可以方便地判断并展示“免费试用 X 天/周/月/年”:
import 'package:nnsdk_with_ad/Entity/CommodityStoreEntity.dart';
String buildIntroFreeTrialText(CommodityStoreEntity store) {
if (!store.isFreeIntroTrial) {
return '';
}
final roundValue = store.introductoryRoundValue ?? 0;
final unitText = store.introductoryRoundUnitText(); // 返回 天/周/月/年/空字符串
if (roundValue <= 0 || unitText.isEmpty) {
return '免费试用';
}
return '$roundValue$unitText 免费试用';
}
在商品卡片中展示示例:
Widget buildIntroTag(CommodityStoreEntity store) {
final text = buildIntroFreeTrialText(store);
if (text.isEmpty) return const SizedBox.shrink();
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.redAccent,
borderRadius: BorderRadius.circular(10),
),
child: Text(
text,
style: const TextStyle(color: Colors.white, fontSize: 12),
),
);
}
5.3 展示按期付款优惠(Pay As You Go)
对于 introductoryPaymentMode == "payAsYouGo" 的商品,可以利用 isPayAsYouGoIntro 来展示“前 X 期 Y 元/期”之类的文案:
String buildPayAsYouGoText(CommodityStoreEntity store) {
if (!store.isPayAsYouGoIntro) {
return '';
}
final currency = store.storeCurrency ?? 'CNY';
final introPrice = store.introductoryOfferPrice ?? 0;
final roundValue = store.introductoryRoundValue ?? 0;
final unitText = store.introductoryRoundUnitText(); // 天/周/月/年
if (roundValue <= 0 || unitText.isEmpty) {
return '限时优惠价:$introPrice $currency';
}
return '前$roundValue$unitText $introPrice $currency / $unitText';
}
你可以在 UI 中同时展示原价与首期优惠,例如:
Widget buildPriceSection(CommodityStoreEntity store) {
final currency = store.storeCurrency ?? 'CNY';
final originPrice = store.storePrice ?? 0;
final payAsYouGoText = buildPayAsYouGoText(store);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (payAsYouGoText.isNotEmpty)
Text(
payAsYouGoText,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.redAccent,
),
),
const SizedBox(height: 2),
Text(
'原价:$originPrice $currency',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
decoration: TextDecoration.lineThrough,
),
),
],
);
}
6. 广告组件
nnsdk 提供了两个广告 Widget,封装了野马广告 SDK 的原生广告和开屏广告能力。
6.1 NNAdWidget(原生信息流广告)
NNAdWidget 用于在页面中展示原生信息流广告(App 自渲染),支持多种布局样式。广告加载完成前不占用空间,加载成功后自动展示。
基本用法:
import 'package:nnsdk_with_ad/NNAdWidget.dart';
NNAdWidget(
placeId: "33001", // 广告位 ID(必填)
width: 345, // 广告宽度
height: 87, // 广告高度
viewStyle: "common", // 布局样式:common / homeBottom / countTopBanner / skinList / personalList
margin: EdgeInsets.only(bottom: 9),
onAdLoaded: () {
print("广告加载成功");
},
onAdFailed: () {
print("广告加载失败");
},
onClose: () {
print("广告关闭");
},
)
参数说明:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
placeId |
String | 是 | 广告位 ID |
width |
double | 否 | 广告视图宽度 |
height |
double | 否 | 广告视图高度 |
viewStyle |
String | 否 | 布局样式,决定原生视图的排版方式 |
identifier |
String | 否 | 页面标识,用于广告上报 |
margin |
EdgeInsetsGeometry | 否 | 外边距 |
adBackgroundColor |
String | 否 | 背景色,十六进制字符串如 "#FFFFFF" |
adBorderRadius |
int | 否 | 广告容器圆角,默认 14 |
adBorderColor |
String | 否 | 广告图片描边颜色,十六进制如 "#F8F8F8" |
adBorderWidth |
double | 否 | 广告图片描边宽度(pt) |
adImageBorderRadius |
int | 否 | 广告图片描边圆角 |
onAdLoaded |
VoidCallback | 否 | 广告加载成功回调 |
onAdFailed |
VoidCallback | 否 | 广告加载失败回调 |
onClose |
VoidCallback | 否 | 用户点击关闭按钮回调 |
注意事项:
NNAdWidget仅支持 iOS 平台,Android 会返回SizedBox.shrink()。- 广告在加载完成前通过
Offstage隐藏,不占用页面布局空间。 - 使用
GlobalKey可以在 widget 树移动时保持原生视图状态,避免重复加载。 - 广告关闭后 widget 会自动隐藏(内部管理
_isVisible状态)。
6.2 NNSplashAdWidget(开屏广告)
NNSplashAdWidget 用于展示全屏开屏广告,通常在 App 启动时使用。
基本用法:
import 'package:nnsdk_with_ad/NNAdWidget.dart';
NNSplashAdWidget(
placeId: "your_splash_ad_place_id", // 广告位 ID(必填)
onAdShowed: () {
print("开屏广告已展示");
},
onAdFail: (String error) {
print("开屏广告加载失败: $error");
},
onClose: () {
print("开屏广告已关闭");
// 跳转到首页
},
)
参数说明:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
placeId |
String | 是 | 开屏广告位 ID |
onAdShowed |
VoidCallback | 否 | 广告展示成功回调 |
onAdFail |
Function(String) | 否 | 广告加载失败回调,返回错误信息 |
onClose |
VoidCallback | 否 | 广告关闭回调(用户点击跳过或倒计时结束) |
典型使用场景:
class SplashPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: NNSplashAdWidget(
placeId: "your_splash_ad_place_id",
onClose: () {
Navigator.of(context).pushReplacementNamed('/home');
},
onAdFail: (error) {
// 广告加载失败,直接进入首页
Navigator.of(context).pushReplacementNamed('/home');
},
),
);
}
}
注意事项:
NNSplashAdWidget使用SizedBox.expand()填满父容器,请确保父容器有明确尺寸。- 广告关闭或加载失败后 widget 会自动隐藏。
- 建议在
onClose和onAdFail中都处理页面跳转逻辑,确保用户不会卡在开屏页。
7. 常见注意事项
- 务必先调用
initSdk并确保成功后,再进行支付、恢复购买等操作。 - 确保
ConfigEntity与BridgingEntity中的各项配置(尤其是域名、接口路径、产品 ID 等)与后端实际环境保持一致。 - 生产环境请关闭
isDebug或减少日志输出,避免泄露敏感信息。
以上示例仅为展示文案示例,你可以根据产品需求自由调整样式与文案,如在“免费试用”之后追加自动续费说明等。
Libraries
- CryptUtil
- Entity/AttributionEntity
- Entity/BridgingEntity
- YApi QuickType插件生成,具体参考文档:https://plugins.jetbrains.com/plugin/18847-yapi-quicktype/documentation
- Entity/CommodityItemEntity
- Entity/CommodityStoreEntity
- Entity/ConfigEntity
- Entity/DeviceInfoEntity
- Entity/InitResultEntity
- Entity/PayResultEntity
- Entity/RestoreResultEntity
- YApi QuickType插件生成,具体参考文档:https://plugins.jetbrains.com/plugin/18847-yapi-quicktype/documentation
- Entity/UserLoginEntity
- GlobalKey
- http/NnRequest
- NativeUseFlutter
- NNAdWidget
- nnsdk
- nnsdk_method_channel
- nnsdk_platform_interface
- NNUtil
- TrackEventRule
- TrackEventUtil