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

如何在Flutter中使用二维码数据保存手机联系人

Flutter 解析二维码并保存联系人到本地

1. 添加依赖

在pubspec.yaml中添加以下依赖:

dependencies:
  flutter:
    sdk: flutter
  qr_code_scanner: ^1.0.1  # 二维码扫描
  contacts_service: ^0.6.3  # 联系人操作
  permission_handler: ^10.2.0  # 权限申请

执行flutter pub get完成依赖安装。

2. 配置平台权限

  • Android:在android/app/src/main/AndroidManifest.xml中添加权限声明:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
  • iOS:在ios/Runner/Info.plist中添加权限描述:
<key>NSCameraUsageDescription</key>
<string>需要使用相机扫描二维码</string>
<key>NSContactsUsageDescription</key>
<string>需要访问通讯录保存联系人</string>

3. 实现核心逻辑

以下是完整的功能实现代码:

import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:contacts_service/contacts_service.dart';
import 'package:permission_handler/permission_handler.dart';

class QrContactSaver extends StatefulWidget {
  const QrContactSaver({super.key});

  @override
  State<QrContactSaver> createState() => _QrContactSaverState();
}

class _QrContactSaverState extends State<QrContactSaver> {
  final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
  QRViewController? controller;
  bool _hasPermissions = false;

  @override
  void initState() {
    super.initState();
    _checkPermissions();
  }

  // 检查并申请必要权限
  Future<void> _checkPermissions() async {
    final cameraStatus = await Permission.camera.status;
    final contactsStatus = await Permission.contacts.status;

    if (!cameraStatus.isGranted || !contactsStatus.isGranted) {
      final result = await [Permission.camera, Permission.contacts].request();
      setState(() {
        _hasPermissions = result[Permission.camera]!.isGranted && result[Permission.contacts]!.isGranted;
      });
    } else {
      setState(() {
        _hasPermissions = true;
      });
    }
  }

  // 解析vCard格式的二维码数据
  Contact? _parseVCard(String vCardData) {
    final lines = vCardData.split('\n');
    String? name;
    String? phone;

    for (var line in lines) {
      if (line.startsWith('FN:')) {
        name = line.substring(3);
      } else if (line.startsWith('TEL:')) {
        phone = line.substring(4).replaceAll(RegExp(r'[^0-9+]'), '');
      }
    }

    if (name != null && phone != null) {
      return Contact(
        displayName: name,
        phones: [Item(label: 'mobile', value: phone)],
      );
    }
    return null;
  }

  // 保存联系人到本地通讯录
  Future<void> _saveContact(Contact contact) async {
    try {
      await ContactsService.addContact(contact);
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('联系人保存成功')),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('保存失败: ${e.toString()}')),
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    if (!_hasPermissions) {
      return Scaffold(
        appBar: AppBar(title: const Text('扫码保存联系人')),
        body: Center(
          child: ElevatedButton(
            onPressed: _checkPermissions,
            child: const Text('申请权限'),
          ),
        ),
      );
    }

    return Scaffold(
      appBar: AppBar(title: const Text('扫码保存联系人')),
      body: Column(
        children: [
          Expanded(
            flex: 5,
            child: QRView(
              key: qrKey,
              onQRViewCreated: (controller) {
                this.controller = controller;
                controller.scannedDataStream.listen((scanData) {
                  controller.pauseCamera();
                  final contact = _parseVCard(scanData.code!);
                  if (contact != null) {
                    _saveContact(contact);
                  } else {
                    if (mounted) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(content: Text('二维码不是有效的联系人数据')),
                      );
                    }
                  }
                  // 2秒后恢复扫描,可根据需求调整
                  Future.delayed(const Duration(seconds: 2), () {
                    controller.resumeCamera();
                  });
                });
              },
            ),
          ),
          const Expanded(
            flex: 1,
            child: Center(
              child: Text('将二维码对准扫描框'),
            ),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    controller?.dispose();
    super.dispose();
  }
}

关键说明

  • 二维码格式要求:代码默认解析标准vCard格式的二维码(包含FN:姓名字段和TEL:电话字段),如果是自定义格式,需修改_parseVCard方法的解析逻辑。
  • 权限前置:必须先获取相机和通讯录权限,否则无法完成扫描或保存操作。
  • 异常处理:代码包含基础错误捕获,可根据业务需求扩展更多异常场景的处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 07:45:34