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

Flutter实现从系统默认通讯录直接选择联系人

Flutter调用系统默认通讯录选择联系人的实现方案

核心思路

通过Flutter的MethodChannel调用原生平台的系统联系人选择器,直接唤起系统默认通讯录应用完成选择,无需在应用内加载全部联系人列表。


1. 配置权限

安卓端(AndroidManifest.xml)

在android/app/src/main/AndroidManifest.xml中添加权限:

<uses-permission android:name="android.permission.READ_CONTACTS" />

iOS端(Info.plist)

在ios/Runner/Info.plist中添加权限描述:

<key>NSContactsUsageDescription</key>
<string>需要访问通讯录以选择联系人</string>

2. 实现平台通道(MethodChannel)

Flutter端代码

import 'package:flutter/services.dart';

class ContactPicker {
  static const MethodChannel _channel = MethodChannel('contact_picker');

  // 唤起系统联系人选择器
  static Future<Map<String, String>?> pickContact() async {
    try {
      final result = await _channel.invokeMethod('pickContact');
      if (result != null) {
        return {
          'name': result['name'],
          'phone': result['phone'],
        };
      }
      return null;
    } on PlatformException catch (e) {
      print('选择联系人失败: ${e.message}');
      return null;
    }
  }
}

// 使用示例
void _pickContact() async {
  final contact = await ContactPicker.pickContact();
  if (contact != null) {
    print('选择的联系人: ${contact['name']}, ${contact['phone']}');
    // 处理选中的联系人信息
  }
}

安卓端原生代码(MainActivity.kt)

import android.content.Intent
import android.provider.ContactsContract
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
    private val CHANNEL = "contact_picker"
    private val REQUEST_CODE_PICK_CONTACT = 1001

    private var resultCallback: MethodChannel.Result? = null

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            when (call.method) {
                "pickContact" -> {
                    resultCallback = result
                    val pickContactIntent = Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI)
                    startActivityForResult(pickContactIntent, REQUEST_CODE_PICK_CONTACT)
                }
                else -> result.notImplemented()
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_CODE_PICK_CONTACT && resultCode == RESULT_OK && data != null) {
            val cursor = contentResolver.query(data.data!!, null, null, null, null)
            cursor?.let {
                if (it.moveToFirst()) {
                    val name = it.getString(it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
                    val phone = it.getString(it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER))
                    resultCallback?.success(mapOf("name" to name, "phone" to phone))
                } else {
                    resultCallback?.error("EMPTY", "未选中联系人", null)
                }
                it.close()
            } ?: run {
                resultCallback?.error("ERROR", "获取联系人失败", null)
            }
            resultCallback = null
        } else {
            resultCallback?.error("CANCEL", "用户取消选择", null)
            resultCallback = null
        }
    }
}

iOS端原生代码(AppDelegate.swift)

import UIKit
import Flutter
import ContactsUI

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, CNContactPickerDelegate {
    private let CHANNEL = "contact_picker"
    private var resultCallback: FlutterResult?

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        let channel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger)
        channel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
            guard let self = self else { return }
            switch call.method {
            case "pickContact":
                self.resultCallback = result
                let picker = CNContactPickerViewController()
                picker.delegate = self
                picker.displayedPropertyKeys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]
                controller.present(picker, animated: true, completion: nil)
            default:
                result(FlutterMethodNotImplemented)
            }
        })
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
        var name = ""
        if !contact.givenName.isEmpty {
            name += contact.givenName
        }
        if !contact.familyName.isEmpty {
            name += " " + contact.familyName
        }
        name = name.trimmingCharacters(in: .whitespaces)
        
        var phone = ""
        if let phoneNumber = contact.phoneNumbers.first?.value {
            phone = phoneNumber.stringValue
        }
        
        resultCallback?([
            "name": name,
            "phone": phone
        ])
        resultCallback = nil
        picker.dismiss(animated: true)
    }

    func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
        resultCallback?(FlutterError(code: "CANCEL", message: "用户取消选择", details: nil))
        resultCallback = nil
        picker.dismiss(animated: true)
    }
}

3. 注意事项

  • 安卓端需要动态申请权限,可配合permission_handler插件实现权限申请逻辑,避免直接唤起选择器时权限不足崩溃。
  • iOS端需确保在Info.plist中正确配置权限描述,否则应用会被拒。
  • 不同系统版本的联系人选择器可能有细微差异,需在目标版本上测试兼容性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:00:00