求助:Flutter InAppWebView调用mobile_scanner扫码并回传至网页JS
问题描述
我正在开发一款Android/iOS双端Flutter应用,通过InAppWebView加载外部URL,支持外部网页用JavaScript调用获取设备联系人、扫描二维码。目前获取联系人功能已实现,但无法在JS触发的Flutter handler中激活摄像头完成扫码,也没法把扫描结果回传给网页,当前使用mobile_scanner包实现扫码。
需要解决的核心问题:如何在JavaScript触发的Flutter handler中调用摄像头扫码,并将结果返回给网页的JavaScript?
解决方案
要实现该功能,需完成以下关键步骤:
1. 配置摄像头权限
扫码前必须获取摄像头权限,需在原生配置文件中声明权限,并动态申请。
2. 实现扫码弹窗界面
通过mobile_scanner创建扫码界面,用Flutter弹窗(如showModalBottomSheet)展示,扫码成功后关闭弹窗并返回结果。
3. 修改JavaScript Handler逻辑
在scanQRCode的handler中启动扫码流程,等待扫码结果后返回给JavaScript。
修改后的Flutter代码
首先确保添加依赖:
dependencies: flutter: sdk: flutter mobile_scanner: ^4.0.0 permission_handler: ^10.2.0 flutter_inappwebview: ^6.0.0 flutter_contacts: ^1.1.0
核心代码修改:
void main() async{ WidgetsFlutterBinding.ensureInitialized(); if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode); } runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const AppBrowser(); } } class AppBrowser extends StatelessWidget { const AppBrowser({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const SafeArea( child: FetchContacts(), ), ); } } class FetchContacts extends StatefulWidget { const FetchContacts({super.key}); @override State<FetchContacts> createState() => _FetchContacts(); } class _FetchContacts extends State<FetchContacts> { late List contactsListDTO = []; InAppWebViewController? _webViewController; Future<List<Contact>> getPhoneContactsData() async { late List<Contact> contactsList; PermissionStatus permission = await Permission.contacts.status; if (permission == PermissionStatus.granted) { contactsList = await FlutterContacts.getContacts( withProperties: true, withPhoto: false); } return contactsList; } @override void initState() { super.initState(); } Future<void> _askPermissions() async { PermissionStatus permissionStatus = await _getContactPermission(); if (permissionStatus != PermissionStatus.granted) { _handleInvalidPermissions(permissionStatus); } } Future<PermissionStatus> _getContactPermission() async { PermissionStatus permission = await Permission.contacts.status; if (permission != PermissionStatus.granted && permission != PermissionStatus.permanentlyDenied) { PermissionStatus permissionStatus = await Permission.contacts.request(); return permissionStatus; } else { return permission; } } void _handleInvalidPermissions(PermissionStatus permissionStatus) async { if (permissionStatus == PermissionStatus.denied) { print('Access to contact data denied'); await openAppSettings(); } else if (permissionStatus == PermissionStatus.permanentlyDenied) { print('Contact data not available on device or permanently Denied'); await openAppSettings(); } } // 新增:扫码方法,返回扫码结果 Future<String?> _scanQRCode() async { // 请求摄像头权限 PermissionStatus cameraPermission = await Permission.camera.request(); if (cameraPermission != PermissionStatus.granted) { await openAppSettings(); return null; } // 弹出扫码界面 return await showModalBottomSheet<String>( context: context, isScrollControlled: true, builder: (context) { return SizedBox( height: MediaQuery.of(context).size.height * 0.7, child: MobileScanner( controller: MobileScannerController( detectionSpeed: DetectionSpeed.normal, facing: CameraFacing.back, ), onDetect: (capture) { final List<Barcode> barcodes = capture.barcodes; if (barcodes.isNotEmpty) { String? code = barcodes.first.rawValue; if (code != null) { Navigator.pop(context, code); } } }, ), ); }, ); } @override Widget build(BuildContext context) { final GlobalKey webViewKey = GlobalKey(); InAppWebViewSettings settings = InAppWebViewSettings( isInspectable: false, mediaPlaybackRequiresUserGesture: false, allowsInlineMediaPlayback: true, iframeAllowFullscreen: true, safeBrowsingEnabled: false, cacheEnabled: false, clearCache: true, ); return InAppWebView( key: webViewKey, initialSettings: settings, initialUrlRequest: URLRequest( url: WebUri("https://localhost/staticwebapp/index.html")), onReceivedServerTrustAuthRequest: (controller, challenge) async { return ServerTrustAuthResponse( action: ServerTrustAuthResponseAction.PROCEED); }, onPermissionRequest: (InAppWebViewController controller, PermissionRequest permissionRequest) async { print('permissionRequest.resources : ${permissionRequest.resources}'); return PermissionResponse( action: PermissionResponseAction.GRANT, resources: permissionRequest.resources); }, onWebViewCreated: (controller) { _webViewController = controller; controller.addJavaScriptHandler( handlerName: 'fetchDeviceContacts', callback: (args) async { print(args); _askPermissions(); var contactsList = await getPhoneContactsData(); return jsonEncode(contactsList); }); // 修改扫码handler逻辑 controller.addJavaScriptHandler( handlerName: 'scanQRCode', callback: (args) async { print(args); // 调用扫码方法,等待结果 String? qrResult = await _scanQRCode(); // 返回结果给JavaScript return qrResult; }); controller.addJavaScriptHandler( handlerName: 'handlerFooWithArgs', callback: (args) { print(args); }); }, onConsoleMessage: (controller, consoleMessage) { print('onConsoleMessage $consoleMessage'); }, ); } }
原生权限配置
- Android:在
android/app/src/main/AndroidManifest.xml中添加
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
- iOS:在
ios/Runner/Info.plist中添加
<key>NSCameraUsageDescription</key> <string>需要摄像头权限进行二维码扫描</string>
网页JavaScript代码(保持原逻辑即可)
var isFlutterInAppWebViewReady = false; window.addEventListener("flutterInAppWebViewPlatformReady", function(event) { isFlutterInAppWebViewReady = true; alert(isFlutterInAppWebViewReady); if (isFlutterInAppWebViewReady) { const args = [1, true, ['bar', 5], {foo: 'baz'}]; window.flutter_inappwebview.callHandler('scanQRCode', ...args). then(function(result) { console.log(JSON.stringify(result)); alert(JSON.stringify(result)); }); } });
内容的提问来源于stack exchange,提问作者Satya
相关产品推荐
相关产品推荐

