Flutter货币化基础功能插件knt_base_monetized的使用

Flutter货币化基础功能插件knt_base_monetized的使用

knt_base_monetized 是一个帮助处理应用内购买和广告流的辅助库。以下是其基本使用方法。

开始使用

1. 配置Admob ID

确保你已经按照以下参考文档完成了平台设置:

2. 使用依赖注入

建议使用 get_itinjectable 进行依赖注入:

  • 参考注册第三方类型部分来创建包中的BLoC和服务类实例。
  • 这些BLoC和服务类需要是单例(使用 @singleton 注解)。

3. 理解BLoC模式

了解BLoC模式,可以参考库 flutter_bloc

4. 使用 flutter_bloc

  • 在应用级别创建 BlocProvider 以提供此包的BLoC。
  • 使用 BlocBuilderBlocListener 来处理来自这些BLoC的状态。

常见流程

1. 广告(Admob)

步骤1:创建实例
AdmobConfig config = AdmobConfig(
  appId: 'YOUR_APP_ID',
  bannerId: 'YOUR_BANNER_ID',
  interstitialId: 'YOUR_INTERSTITIAL_ID',
);

AdmobService admobService = AdmobService(config);
FrequentlyAdsBloc frequentlyAdsBloc = FrequentlyAdsBloc(admobService);
StaticAdsBloc staticAdsBloc = StaticAdsBloc(admobService);
步骤2:初始化

main() 函数中初始化 MobileAd

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await getIt<AdmobService>().init();
  runApp(App());
}
步骤3:在启动页中准备广告

SplashPageinitState() 中插入以下代码:

Future.delayed(Duration.zero, () {
  context.read<FrequentlyAdsBloc>().add(PrepareFrequentlyAdEvent());
});
步骤4:显示插屏广告

在需要显示插屏广告的地方调用:

context.read<FrequentlyAdsBloc>().add(ShowFrequentlyAdEvent(_adTag));

推荐为每个屏幕创建唯一的 _adTag 以便于状态管理。

2. 应用内购买

步骤1:创建订阅BLoC

创建一个继承自 BaseSubscriptionBloc 的订阅BLoC。

class SubscriptionBloc extends BaseSubscriptionBloc {
  SubscriptionBloc(MonetizedRepo repo, {required String entitlementID})
      : super(repo, entitlementID: entitlementID);

  [@override](/user/override)
  void onInit() {
    super.onInit();
    // 初始化逻辑
  }

  [@override](/user/override)
  void onDispose() {
    super.onDispose();
    // 清理逻辑
  }
}
步骤2:覆盖方法

覆盖所有需要的方法,例如从你的应用内购买库中获取相应函数。

步骤3:创建Premium页面

创建一个展示高级功能的页面,包括SKU列表、恢复购买选项、条款和隐私信息。

步骤4:处理SKU列表和购买请求
  • 显示SKU列表:使用 SubscriptionBloc#FetchListSkuEvent
  • 请求订阅:使用 SubscriptionBloc#RequestSubscriptionEvent
  • 恢复购买:使用 SubscriptionBloc#RestoreSubscriptionEvent
步骤4(可选):处理免费使用流

创建 FreeUsageCounterBloc 实例来处理用户的免费使用流。

注意事项

  • 此包包含 app_tracking_transparency 用于处理iOS上的Admob隐私问题。

示例代码

以下是完整的示例代码:

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:knt_base_monetized/knt_base_monetized.dart';
import 'package:knt_bloc/knt_bloc.dart';

import 'bloc/subscription_bloc.dart';
import 'data/monetize_repo.dart';
import 'data/purchase_service.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Knt Monetized Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BlocProvider(
        create: (BuildContext context) => SubscriptionBloc(
          ArticleMonetizedRepo(),
          entitlementID: Purchases.defaultEntitlementID,
        ),
        child: const MyHomePage(title: 'Knt Monetized Demo'),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _iapTag = (MyHomePage).toString();
  final _logs = <String>[];

  [@override](/user/override)
  void initState() {
    super.initState();
    Future.delayed(Duration.zero, _onFetchSku);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return BlocListener<SubscriptionBloc, BaseState>(
      listener: (context, state) {
        if (!mounted) {
          return;
        }

        void showSnackBar(String message) {
          ScaffoldMessenger.of(context).removeCurrentSnackBar();
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              duration: const Duration(milliseconds: 2811),
              content: Text(message),
            ),
          );
        }

        final stateTag = state.tag;
        final timeStamps = DateTime.now();
        final newMessageLog = '[${DateFormat.Hms().format(timeStamps)}]: '
            '${state.runtimeType} with tag [$stateTag]';
        showSnackBar(newMessageLog);
        setState(() {
          _logs.insert(0, newMessageLog);
        });
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            BlocBuilder<SubscriptionBloc, BaseState>(
              buildWhen: (previous, current) {
                return current is FetchingOfferingsState ||
                    current is FetchedOfferingsState ||
                    current is FetchedOfferingsFailureState;
              },
              builder: (context, state) => switch (state) {
                FetchedOfferingsFailureState() => Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(
                        'Error: ${state.exception.additionalInfo}',
                        style: Theme.of(context).textTheme.bodyLarge,
                      ),
                      const SizedBox(height: 8),
                      TextButton(
                        onPressed: _onFetchSku,
                        child: const Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Icon(
                              Icons.refresh,
                              size: 24,
                            ),
                            SizedBox(width: 8),
                            Text('Retry'),
                          ],
                        ),
                      ),
                    ],
                  ),
                FetchingOfferingsState() => const SizedBox(
                    height: 240,
                    child: Center(
                      child: CircularProgressIndicator(),
                    ),
                  ),
                FetchedOfferingsState(:final data) => Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16),
                    child: _SubscriptionBox(
                      itemList: data
                        ..sort(
                          (s1, s2) => s2.price.compareTo(s1.price),
                        ),
                      onItemPicked: _onItemPicked,
                    ),
                  ),
                _ => const SizedBox(),
              },
            ),
            const SizedBox(height: 16),
            Expanded(
              child: SingleChildScrollView(
                padding: const EdgeInsets.symmetric(horizontal: 16),
                child: Column(
                  children: [
                    for (final (index, log) in _logs.indexed)
                      Row(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Container(
                            width: 20,
                            height: 20,
                            margin: const EdgeInsets.symmetric(vertical: 2),
                            decoration: const BoxDecoration(
                              shape: BoxShape.circle,
                              color: Colors.green,
                            ),
                            child: Center(
                              child: FittedBox(
                                child: Text(
                                  '${_logs.length - index}',
                                  style: Theme.of(context)
                                      .textTheme
                                      .labelSmall
                                      ?.copyWith(color: Colors.white),
                                ),
                              ),
                            ),
                          ),
                          const SizedBox(width: 8),
                          Expanded(
                            child: Text(log),
                          ),
                        ],
                      ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16),
              child: Row(
                children: [
                  Expanded(
                    child: ElevatedButton(
                      onPressed: _onFetchSku,
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.red,
                      ),
                      child: Text(
                        'Retry',
                        style: Theme.of(context)
                            .textTheme
                            .bodyMedium
                            ?.copyWith(color: Colors.white),
                      ),
                    ),
                  ),
                  const SizedBox(width: 16),
                  Expanded(
                    child: ElevatedButton(
                      onPressed: _onRestore,
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.green,
                      ),
                      child: Text(
                        'Restore',
                        style: Theme.of(context)
                            .textTheme
                            .bodyMedium
                            ?.copyWith(color: Colors.white),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            const SizedBox(height: 32),
          ],
        ),
      ),
    );
  }

  void _onFetchSku() {
    _makeLogGap();
    context.read<SubscriptionBloc>().add(FetchOfferingsEvent(tag: _iapTag));
  }

  void _onItemPicked(SubscriptionItem item) {
    _makeLogGap();
    context.read<SubscriptionBloc>().add(
          RequestSubscriptionEvent(
            item,
            tag: _iapTag,
          ),
        );
  }

  void _onRestore() {
    _makeLogGap();
    context
        .read<SubscriptionBloc>()
        .add(RestoreSubscriptionEvent(tag: _iapTag));
  }

  void _makeLogGap() {
    setState(() {
      _logs.insert(0, '---------------------');
    });
  }
}

class _SubscriptionBox extends StatelessWidget {
  const _SubscriptionBox({
    this.itemList = const [],
    this.onItemPicked,
  });

  final List<SubscriptionItem> itemList;
  final ValueSetter<SubscriptionItem>? onItemPicked;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        for (final item in itemList)
          InkWell(
            onTap: () => onItemPicked?.call(item),
            child: Container(
              margin: const EdgeInsets.only(top: 12),
              padding: const EdgeInsets.symmetric(
                horizontal: 16,
                vertical: 8,
              ),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(8),
                border: Border.all(
                  color: (item.period == Period.annually
                          ? Colors.red
                          : Colors.grey)
                      .withOpacity(0.3),
                ),
              ),
              child: Row(
                children: [
                  const Icon(
                    Icons.shopping_cart,
                    size: 24,
                    color: Colors.orange,
                  ),
                  const SizedBox(width: 16),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          item.content,
                          style: Theme.of(context).textTheme.bodyLarge,
                        ),
                        const SizedBox(height: 4),
                        Text(
                          '${item.sku} - ${item.localizedPrice}',
                          style: Theme.of(context)
                              .textTheme
                              .bodyMedium
                              ?.copyWith(color: Colors.green),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        const SizedBox(height: 16),
      ],
    );
  }
}

更多关于Flutter货币化基础功能插件knt_base_monetized的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter货币化基础功能插件knt_base_monetized的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


knt_base_monetized 是一个用于 Flutter 应用货币化的基础功能插件。它提供了一些常见的货币化功能,如广告展示、应用内购买等,帮助开发者更容易地集成和应用这些功能。以下是如何使用 knt_base_monetized 插件的基础指南。

1. 添加依赖

首先,在你的 pubspec.yaml 文件中添加 knt_base_monetized 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  knt_base_monetized: ^latest_version

然后运行 flutter pub get 来安装依赖。

2. 初始化插件

在你的应用启动时,需要初始化 knt_base_monetized 插件。通常可以在 main.dart 文件中进行初始化:

import 'package:flutter/material.dart';
import 'package:knt_base_monetized/knt_base_monetized.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化插件
  await KntBaseMonetized.initialize(
    adMobAppId: 'your_admob_app_id',
    // 其他初始化配置
  );

  runApp(MyApp());
}

3. 展示广告

knt_base_monetized 提供了多种广告类型的支持,如横幅广告、插页式广告、奖励视频广告等。

横幅广告

import 'package:flutter/material.dart';
import 'package:knt_base_monetized/knt_base_monetized.dart';

class BannerAdExample extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Banner Ad Example'),
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView(
              children: [
                // 你的应用内容
              ],
            ),
          ),
          KntBannerAd(
            adUnitId: 'your_banner_ad_unit_id',
          ),
        ],
      ),
    );
  }
}

插页式广告

import 'package:flutter/material.dart';
import 'package:knt_base_monetized/knt_base_monetized.dart';

class InterstitialAdExample extends StatefulWidget {
  [@override](/user/override)
  _InterstitialAdExampleState createState() => _InterstitialAdExampleState();
}

class _InterstitialAdExampleState extends State<InterstitialAdExample> {
  KntInterstitialAd _interstitialAd;

  [@override](/user/override)
  void initState() {
    super.initState();
    _interstitialAd = KntInterstitialAd(adUnitId: 'your_interstitial_ad_unit_id');
    _interstitialAd.load();
  }

  void _showAd() {
    _interstitialAd.show();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Interstitial Ad Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: _showAd,
          child: Text('Show Interstitial Ad'),
        ),
      ),
    );
  }
}

奖励视频广告

import 'package:flutter/material.dart';
import 'package:knt_base_monetized/knt_base_monetized.dart';

class RewardedAdExample extends StatefulWidget {
  [@override](/user/override)
  _RewardedAdExampleState createState() => _RewardedAdExampleState();
}

class _RewardedAdExampleState extends State<RewardedAdExample> {
  KntRewardedAd _rewardedAd;

  [@override](/user/override)
  void initState() {
    super.initState();
    _rewardedAd = KntRewardedAd(adUnitId: 'your_rewarded_ad_unit_id');
    _rewardedAd.load();
  }

  void _showAd() {
    _rewardedAd.show(onReward: (reward) {
      // 处理奖励逻辑
      print('User earned reward: $reward');
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Rewarded Ad Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: _showAd,
          child: Text('Show Rewarded Ad'),
        ),
      ),
    );
  }
}

4. 应用内购买

knt_base_monetized 还支持应用内购买功能。你需要配置产品 ID 并处理购买逻辑。

import 'package:flutter/material.dart';
import 'package:knt_base_monetized/knt_base_monetized.dart';

class InAppPurchaseExample extends StatefulWidget {
  [@override](/user/override)
  _InAppPurchaseExampleState createState() => _InAppPurchaseExampleState();
}

class _InAppPurchaseExampleState extends State<InAppPurchaseExample> {
  List<ProductDetails> _products = [];

  [@override](/user/override)
  void initState() {
    super.initState();
    _loadProducts();
  }

  Future<void> _loadProducts() async {
    final products = await KntInAppPurchase.queryProductDetails([
      'your_product_id_1',
      'your_product_id_2',
    ]);
    setState(() {
      _products = products;
    });
  }

  Future<void> _purchaseProduct(ProductDetails product) async {
    final purchaseDetails = await KntInAppPurchase.buyProduct(product);
    // 处理购买结果
    if (purchaseDetails.status == PurchaseStatus.purchased) {
      print('Purchase successful: ${purchaseDetails.productID}');
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('In-App Purchase Example'),
      ),
      body: ListView.builder(
        itemCount: _products.length,
        itemBuilder: (context, index) {
          final product = _products[index];
          return ListTile(
            title: Text(product.title),
            subtitle: Text(product.price),
            onTap: () => _purchaseProduct(product),
          );
        },
      ),
    );
  }
}
回到顶部