解决Bloc中StateError: Cannot emit new states after calling close错误
解决StateError: Bad state: Cannot emit new states after calling close问题
问题原因
- 扫码流重复触发:
scannedDataStream会持续输出扫码结果,即使已经成功识别一次,仍会多次触发回调,导致重复调用Cubit的scanner方法 - Cubit作用域混乱:在
build方法内创建BlocProvider,页面重建时会重复实例化Cubit,旧Cubit被销毁后仍可能执行异步请求并尝试发射状态 - 未及时停止监听:扫码成功后未暂停相机和取消流订阅,导致后续无效的状态发射请求
修复方案
1. 修改扫码页面(QRViewExample)
import 'dart:developer'; import 'dart:async'; import 'package:eco_city/features/presentation/scanner/cubit/scanner_cubit.dart'; import 'package:eco_city/features/presentation/scanner/cubit/scanner_state.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'dart:io'; import '../home/home.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; class QRViewExample extends StatefulWidget { @override _QRViewExampleState createState() => _QRViewExampleState(); } class _QRViewExampleState extends State<QRViewExample> { final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); Barcode? result; QRViewController? controller; ScannerCubit? _scannerCubit; StreamSubscription? _scanSubscription; bool _isProcessing = false; // 防止重复请求 @override void initState() { super.initState(); _scannerCubit = ScannerCubit(); // 初始化Cubit,避免build重复创建 } @override void reassemble() { super.reassemble(); print('первая функция'); if (Platform.isAndroid) { controller?.pauseCamera(); } else if (Platform.isIOS) { controller?.resumeCamera(); } } String? getQr(Barcode? qr) { if (qr != null) { RegExp exp = RegExp(r"[^0-9]+"); final stringFormat = qr.rawContent.replaceAll(exp, ''); return stringFormat; } return null; } @override Widget build(BuildContext context) { return BlocProvider.value( value: _scannerCubit!, child: BlocBuilder<ScannerCubit, ScannerState>( builder: (context, state) { if (state is ScannerNotScan) { return Scaffold( body: Column( children: <Widget>[ Expanded( flex: 5, child: QRView( key: qrKey, onQRViewCreated: _onQRViewCreated, ), ), Expanded( flex: 1, child: Center( child: (result != null) ? Text( 'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.rawContent}') : Text('Scan a code'), ), ) ], ), ); } else if (state is ScannerLoading) { return Scaffold( body: Center(child: CircularProgressIndicator()), ); } else if (state is ScannerLoaded) { return Home(); } else if (state is ScannerError) { return Scaffold( body: Center(child: Text('扫码失败,请重试')), ); } throw StateError('无效状态'); }, ), ); } void _onQRViewCreated(QRViewController controller) async { print('последняя функция'); this.controller = controller; _scanSubscription = controller.scannedDataStream.listen((scanData) { if (_isProcessing) return; // 正在处理时忽略新结果 setState(() { result = scanData; }); _scanSubscription?.cancel(); // 取消订阅,避免重复触发 controller.pauseCamera(); // 暂停相机 getNextScreen(); }); } void getNextScreen() async { if (result != null && !_isProcessing) { _isProcessing = true; final qrCode = getQr(result!); if (qrCode != null) { await context.read<ScannerCubit>().scanner(qrCode); } _isProcessing = false; } } @override void dispose() { _scanSubscription?.cancel(); // 清理订阅 controller?.dispose(); _scannerCubit?.close(); // 手动关闭Cubit super.dispose(); } }
2. 修改ScannerCubit
import 'dart:convert'; import 'dart:developer'; import 'package:eco_city/features/data/scanner/scanner_models.dart'; import 'package:eco_city/features/presentation/scanner/cubit/scanner_state.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import '../../config.dart'; class ScannerCubit extends Cubit<ScannerState> { ScannerCubit() : super(ScannerNotScan()); Future<void> scanner(String qr) async { // 先检查Cubit是否已关闭,避免后续无效操作 if (isClosed) return; emit(ScannerLoading()); final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('token'); final response = await http.post(Uri.parse(ConfigUrl.sendQrCodeScanner), headers: { "Content-Type": "application/vnd.api+json", "Accept": "application/vnd.api+json", "Authorization": "Bearer $token" }, body: jsonEncode<String, dynamic>({ "qr_code": qr, })); // 请求完成后再次检查是否关闭 if (isClosed) return; if (response.statusCode == 200) { log(qr); final responseJson = json.decode(response.body) as Map<String, dynamic>; emit(ScannerLoaded(scannerModel: ScannerModel.fromJson(responseJson))); } else { log(qr); log(response.statusCode.toString()); log(json.decode(response.body).toString()); emit(ScannerError()); } } }
关键修复点
- 将Cubit初始化移到
initState,使用BlocProvider.value提供实例,避免页面重建重复创建Cubit - 扫码成功后立即取消
scannedDataStream订阅并暂停相机,防止重复触发逻辑 - 添加
_isProcessing标记,避免同一时间发起多个请求 - 在Cubit的
scanner方法开头和请求完成后都检查isClosed,确保不会在Cubit关闭后发射状态
内容的提问来源于stack exchange,提问作者Daniil
相关产品推荐
相关产品推荐

