You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用BLoC实现in_app_purchase内购并解决emit调用报错问题

问题原因
  • 你遇到的emit was called after an event handler completed normally报错,核心原因是_onStarted事件处理函数为同步函数,执行完成后BLoC会自动关闭本次事件的emit作用域,而你在purchaseStream的异步监听回调中调用emit时,该作用域已经失效,因此触发报错。
  • 次要逻辑问题:代码中多次使用forEach嵌套async回调,这类异步操作不会被外层逻辑等待,会导致执行顺序混乱,例如completePurchase异步执行未完成就提前emit、商品初始化未完成用户就触发购买等异常。
修复方案
  1. 使用emit.forEach管理购买流的监听,BLoC会自动持有emit的有效生命周期,直到流结束或Bloc销毁
  2. 替换所有forEach+async的写法为await for,保证异步操作按顺序执行
  3. 异步初始化方法添加await逻辑,避免时序问题
  4. 所有emit前可增加emit.isClosed判断,双重保障不会在作用域失效后调用emit
修复后完整代码
class PaymentBloc extends Bloc<PaymentEvent, PaymentState> {
  static const bool _started = false;
  final InAppPurchase _inAppPurchase = InAppPurchase.instance;
  final Set<String> _kProductIds = {"mini_burger_4_99"};
  List<ProductDetails> _products = [];
  StreamSubscription<List<PurchaseDetails>>? _subscription;

  PaymentBloc() : super(PaymentInitial(_started)) {
    on<PaymentStarted>(_onStarted);
    on<PaymentCompletion>(_buyProduct);
  }

  Future<void> _onStarted(PaymentStarted event, Emitter<PaymentState> emit) async {
    await initStoreInfo();
    final purchaseUpdated = _inAppPurchase.purchaseStream;
    // 使用emit.forEach管理流生命周期,自动持有emit权限
    await emit.forEach<List<PurchaseDetails>>(
      purchaseUpdated,
      onData: (purchaseDetailsList) async* {
        await for (final purchaseDetails in purchaseDetailsList) {
          if (purchaseDetails.status == PurchaseStatus.pending) {
            yield PaymentPending();
          } else {
            if (purchaseDetails.status == PurchaseStatus.error) {
              yield PaymentError();
            } else if (purchaseDetails.pendingCompletePurchase) {
              await _inAppPurchase.completePurchase(purchaseDetails);
              yield PaymentComplete();
            }
          }
        }
      },
      onError: (error, stackTrace) {
        return PaymentError();
      },
    );
  }

  Future<void> _buyProduct(
      PaymentCompletion event, Emitter<PaymentState> emit) async {
    final paymentWrapper = SKPaymentQueueWrapper();
    final transactions = await paymentWrapper.transactions();
    // 替换forEach+async为await for保证异步操作顺序
    await for (final transaction in transactions) {
      await paymentWrapper.finishTransaction(transaction);
    }
    try {
      final purchaseParam = PurchaseParam(
          productDetails:
              _products.firstWhere((product) => product.id == event.id));
      await _inAppPurchase.buyConsumable(purchaseParam: purchaseParam);
    } catch (e) {
      if (!emit.isClosed) {
        emit(PaymentError());
      }
    }
  }

  @override
  Future<void> close() {
    _subscription?.cancel();
    return super.close();
  }

  Future<void> initStoreInfo() async {
    final bool isAvailable = await _inAppPurchase.isAvailable();
    if (!isAvailable) {
      _products = [];
      return;
    }
    if (Platform.isIOS) {
      final iosPlatformAddition = _inAppPurchase
          .getPlatformAddition<InAppPurchaseIosPlatformAddition>();
      await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate());
    }
    final productDetailResponse =
        await _inAppPurchase.queryProductDetails(_kProductIds);
    _products = productDetailResponse.productDetails;
  }
}
状态优化建议

如果你只需要布尔值控制UI,可将状态类合并为单类,简化调用:

class PaymentState {
  final bool isPending;
  final bool isSuccess;
  final bool isError;

  PaymentState({
    this.isPending = false,
    this.isSuccess = false,
    this.isError = false,
  });

  PaymentState copyWith({
    bool? isPending,
    bool? isSuccess,
    bool? isError,
  }) {
    return PaymentState(
      isPending: isPending ?? this.isPending,
      isSuccess: isSuccess ?? this.isSuccess,
      isError: isError ?? this.isError,
    );
  }
}
// emit时直接更新对应字段即可:emit(state.copyWith(isPending: true))

内容的提问来源于stack exchange,提问作者Matt199

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 08:36:04