Flutter下拉菜单加载联系人出现_AssertionError问题求助
错误原因
- 初始默认值
"Select a contact"未添加到下拉选项列表中,导致DropdownButton找不到对应item触发断言错误。 - 使用联系人的
displayName作为DropdownMenuItem的value,当存在同名联系人时,会出现多个相同value的item,违反DropdownButton要求value必须对应唯一item的规则。
解决步骤
1. 调整下拉选项的value类型
将DropdownMenuItem的value类型从String改为Contact,直接使用联系人实例作为value,确保每个选项的value唯一,即使名字重复也不会冲突。
2. 添加默认选择项
在加载联系人后,给下拉选项列表添加一个默认的"Select a contact"选项,确保初始value能在列表中找到对应的item。
3. 修正状态变量与逻辑
- 更新
_selectedContact的初始值为null,配合默认选项。 - 调整初始化、联系人加载、选择回调的逻辑,适配
Contact类型的value。
修改后的完整代码
import 'package:flutter/material.dart'; import 'package:contacts_service/contacts_service.dart'; import 'package:permission_handler/permission_handler.dart'; import 'dart:io'; import 'dart:convert'; import 'package:url_launcher/url_launcher.dart'; import 'package:path_provider/path_provider.dart'; class Interface extends StatelessWidget { const Interface({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('pAM'), ), body: const ContactSelector(), ); } } class ContactSelector extends StatefulWidget { const ContactSelector({super.key}); @override _ContactSelectorState createState() => _ContactSelectorState(); } class _ContactSelectorState extends State<ContactSelector> { Contact? _selectedContact; // 改为可空类型,初始为null对应默认选项 late bool _isTrue; late Iterable<Contact> _contacts; List<DropdownMenuItem<Contact?>> _dropdownItems = []; @override void initState() { super.initState(); _getContacts(); } @override Widget build(BuildContext context) { return Column( children: <Widget>[ DropdownButton<Contact?>( value: _selectedContact, items: _dropdownItems, hint: const Text("Loading..."), // 加载时显示hint onChanged: (newValue) { _onContactChanged(newValue); }, ) ], ); } String? encodeQueryParameters(Map<String, String> params) { return params.entries .map((e) => '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}') .join('&'); } void _sendMessage(String message) async { if (_selectedContact?.phones == null || _selectedContact!.phones!.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("该联系人无可用电话号码")), ); return; } // 取第一个电话号码 String phoneNumber = _selectedContact!.phones!.first.value!; Uri uri = Uri( scheme: 'sms', path: phoneNumber, query: encodeQueryParameters(<String, String>{ 'body': message, }), ); if (await canLaunchUrl(uri)) { await launchUrl(uri); // 修正:之前是重复调用canLaunchUrl,改为launchUrl } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("无法发送短信")), ); } } _getContacts() async { // 先请求权限 var status = await Permission.contacts.request(); if (!status.isGranted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("需要联系人权限才能加载联系人")), ); return; } _contacts = await ContactsService.getContacts(withThumbnails: false); // 构建下拉选项,先添加默认选项 _dropdownItems = [ const DropdownMenuItem( value: null, child: Text("Select a contact"), ), ..._contacts.map((c) => DropdownMenuItem( value: c, child: Text(c.displayName ?? "未命名联系人"), )), ]; setState(() {}); } _onContactChanged(Contact? newValue) { setState(() { _selectedContact = newValue; }); if (_selectedContact != null) { _saveContactToFile(_selectedContact!); _readJson(); } } _saveContactToFile(Contact contact) async { final directory = await getApplicationDocumentsDirectory(); final file = File('${directory.path}/selected_contact.txt'); await file.writeAsString(jsonEncode(contact.toMap())); } void _readJson() async { final directory = await getApplicationDocumentsDirectory(); final file = File('${directory.path}/true.json'); if (await file.exists()) { final content = jsonDecode(await file.readAsString()); _isTrue = content["isTrue"] ?? false; // 初始化_isTrue if (_isTrue) { _promptMessage(); } else { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Reminder'), content: const Text( "You can continue your work, remember your loved ones misses you"), actions: <Widget>[ ElevatedButton( child: const Text('OK'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }); } } } _promptMessage() { if (_isTrue) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Select a message'), content: SingleChildScrollView( child: ListBody( children: <Widget>[ InkWell( child: const Text('How are you?'), onTap: () { _sendMessage('How are you?'); Navigator.of(context).pop(); }), InkWell( child: const Text('What are you up to?'), onTap: () { _sendMessage('What are you up to?'); Navigator.of(context).pop(); }), InkWell( child: const Text('What is for dinner?'), onTap: () { _sendMessage('What is for dinner?'); Navigator.of(context).pop(); }), ], ), ), actions: <Widget>[ ElevatedButton( child: const Text('Cancel'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); } } }
额外优化点
- 添加了联系人权限请求逻辑,避免无权限时崩溃。
- 修复了
_sendMessage中重复调用canLaunchUrl的错误,改为正确调用launchUrl。 - 处理了联系人无电话号码的情况,添加提示。
- 给未命名联系人显示默认文本,避免空指针。
- 在选择消息后关闭弹窗,提升用户体验。
内容的提问来源于stack exchange,提问作者thegaffoor
相关产品推荐
相关产品推荐

