Dart SDK version is 3.7.01
dependencies:flutter:sdk: flutterpermission_handler: ^11.0.1 # 权限管理flutter_contacts: ^1.1.9+2call_log: ^5.0.5cupertino_icons: ^1.0.8dev_dependencies:flutter_test:sdk: flutterflutter_lints: ^5.0.0
2 contact_and_calls_page.dartimport 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:call_log/call_log.dart';class ContactAndCallsPage extends StatefulWidget {@override_ContactAndCallsPageState createState() => _ContactAndCallsPageState(); }class _ContactAndCallsPageState extends State<ContactAndCallsPage>with SingleTickerProviderStateMixin {List<Contact> _contacts = [];Iterable<CallLogEntry> _callLogs = [];bool _isLoading = true;String _errorMessage = '';late TabController _tabController;@overridevoid initState() {super.initState();_tabController = TabController(length: 2, vsync: this);_requestPermissions();}@overridevoid dispose() {_tabController.dispose();super.dispose();}// 请求权限Future<void> _requestPermissions() async {setState(() {_isLoading = true;_errorMessage = '';});try {// Android 特定权限处理var contactStatus = await Permission.contacts.request();var phoneStatus = await Permission.phone.request();// 检查是否获得权限if (contactStatus.isGranted || phoneStatus.isGranted) {await _fetchContacts();await _fetchCallLogs();} else {setState(() {_errorMessage = '需要通讯录和通话记录权限以正常使用功能';});_showPermissionDeniedDialog();}} catch (e) {setState(() {_errorMessage = '权限请求发生错误: $e';});print('权限请求错误: $e');} finally {setState(() {_isLoading = false;});}}// 显示权限被拒绝的对话框void _showPermissionDeniedDialog() {showDialog(context: context,builder: (context) => AlertDialog(title: Text('权限受限'),content: Text(_errorMessage),actions: [TextButton(onPressed: () {Navigator.of(context).pop();openAppSettings(); // 打开应用设置},child: Text('打开设置'),),TextButton(onPressed: () => Navigator.of(context).pop(),child: Text('取消'),),],),);}// 获取通讯录联系人Future<void> _fetchContacts() async {try {// 多重权限检查bool permission = await FlutterContacts.requestPermission(readonly: true);if (permission) {// 添加详细配置获取联系人final contacts = await FlutterContacts.getContacts(withProperties: true, // 获取联系人属性withPhoto: false, // 不获取照片以提高性能sorted: true, // 按名称排序);// 过滤掉没有电话号码的联系人final filteredContacts = contacts.where((contact) =>contact.phones.isNotEmpty).toList();setState(() {_contacts = filteredContacts;});print('获取到联系人数量: ${_contacts.length}');} else {setState(() {_errorMessage = '未获得通讯录权限';});}} catch (e) {print('获取通讯录错误: $e');setState(() {_errorMessage = '获取通讯录失败: $e';});}}// 获取通话记录Future<void> _fetchCallLogs() async {try {// 获取通话记录Iterable<CallLogEntry> callLogs = await CallLog.get();// 过滤最近30天的通话记录final now = DateTime.now();final thirtyDaysAgo = now.subtract(Duration(days: 30));setState(() {_callLogs = callLogs.where((log) {final logTime = DateTime.fromMillisecondsSinceEpoch(log.timestamp ?? 0);return logTime.isAfter(thirtyDaysAgo);}).take(100) // 限制最多100条记录.toList();});print('获取到通话记录数量: ${_callLogs.length}');} catch (e) {print('获取通话记录错误: $e');setState(() {_errorMessage = '获取通话记录失败: $e';});}}// 转换通话类型String _getCallType(CallType? callType) {switch (callType) {case CallType.incoming:return '呼入';case CallType.outgoing:return '呼出';case CallType.missed:return '未接';default:return '未知';}}// 格式化通话记录时间String _formatCallLogTime(int? timestamp) {if (timestamp == null) return '未知时间';final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);return '${dateTime.year}-${_twoDigits(dateTime.month)}-${_twoDigits(dateTime.day)} ''${_twoDigits(dateTime.hour)}:${_twoDigits(dateTime.minute)}:${_twoDigits(dateTime.second)}';}// 补充两位数方法String _twoDigits(int n) {return n.toString().padLeft(2, '0');}// 格式化通话时长String _formatCallDuration(int? duration) {if (duration == null || duration == 0) return '未接通';final minutes = duration ~/ 60;final seconds = duration % 60;return '$minutes分${_twoDigits(seconds)}秒';}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('通讯录和通话记录'),bottom: TabBar(controller: _tabController,tabs: [Tab(icon: Icon(Icons.contacts), text: '通讯录'),Tab(icon: Icon(Icons.call), text: '通话记录'),],),actions: [IconButton(icon: Icon(Icons.refresh),onPressed: _requestPermissions, // 直接调用权限请求方法),],),body: _isLoading? Center(child: CircularProgressIndicator()): _errorMessage.isNotEmpty? Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Icon(Icons.error_outline, color: Colors.red, size: 100),SizedBox(height: 20),Text(_errorMessage,style: TextStyle(color: Colors.red),textAlign: TextAlign.center,),SizedBox(height: 20),ElevatedButton(onPressed: _requestPermissions,child: Text('重新获取权限'),)],),): TabBarView(controller: _tabController,children: [_buildContactsList(),_buildCallLogsList(),],),);}// 构建通讯录列表Widget _buildContactsList() {if (_contacts.isEmpty) {return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Icon(Icons.contacts, size: 100, color: Colors.grey),SizedBox(height: 20),Text('暂无联系人',style: TextStyle(fontSize: 18, color: Colors.grey),),],),);}return ListView.builder(itemCount: _contacts.length,itemBuilder: (context, index) {Contact contact = _contacts[index];return ListTile(leading: CircleAvatar(backgroundColor: Colors.primaries[index % Colors.primaries.length],child: Text(contact.name.first.isNotEmpty ? contact.name.first[0] : '?',style: TextStyle(fontWeight: FontWeight.bold,color: Colors.white,),),),title: Text(contact.name.first.isNotEmpty ? contact.name.first : '未命名',style: TextStyle(fontWeight: FontWeight.bold),),subtitle: Text(contact.phones.isNotEmpty? contact.phones.first.number: '无电话号码',style: TextStyle(color: Colors.grey[600]),),);},);}// 构建通话记录列表Widget _buildCallLogsList() {if (_callLogs.isEmpty) {return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Icon(Icons.call, size: 100, color: Colors.grey),SizedBox(height: 20),Text('暂无通话记录',style: TextStyle(fontSize: 18, color: Colors.grey),),],),);}return ListView.builder(itemCount: _callLogs.length,itemBuilder: (context, index) {CallLogEntry callLog = _callLogs.elementAt(index);return ListTile(leading: Icon(callLog.callType == CallType.missed? Icons.call_missed: (callLog.callType == CallType.incoming? Icons.call_received: Icons.call_made),color: callLog.callType == CallType.missed? Colors.red: Colors.green,),title: Text(callLog.number ?? '未知号码'),subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [Text('时间: ${_formatCallLogTime(callLog.timestamp)}'),Text('类型: ${_getCallType(callLog.callType)}'),Text('通话时长: ${_formatCallDuration(callLog.duration)}'),],),);},);} }
3 main.dart
import 'package:flutter/material.dart'; import 'contact_and_calls_page.dart';void main() {runApp(MyApp()); }class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: '通讯录和通话记录',theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,),home: ContactAndCallsPage(),);} }
4 降级java sdk到1.8 --- build.gradle.kts
plugins {id("com.android.application")id("kotlin-android")id("dev.flutter.flutter-gradle-plugin") }android {namespace = "com.example.contactlist"compileSdk = flutter.compileSdkVersionndkVersion = flutter.ndkVersioncompileOptions {// Java 8 配置sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8// 启用 Core Library DesugaringisCoreLibraryDesugaringEnabled = true}kotlinOptions {// 匹配 Java 版本jvmTarget = JavaVersion.VERSION_1_8.toString()}defaultConfig {applicationId = "com.example.contactlist"minSdk = 21 // 确保 minSdk 不低于 21targetSdk = flutter.targetSdkVersionversionCode = flutter.versionCodeversionName = flutter.versionName// 启用 MultiDexmultiDexEnabled = true}buildTypes {release {signingConfig = signingConfigs.getByName("debug")}} }dependencies {// 升级到 2.1.4 或更高版本coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")// MultiDex 支持implementation("androidx.multidex:multidex:2.0.1") }flutter {source = "../.." }