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

Flutter下拉菜单加载联系人出现_AssertionError问题求助

问题解决:DropdownButton重复联系人选择与初始化错误

错误原因

  1. 初始默认值"Select a contact"未添加到下拉选项列表中,导致DropdownButton找不到对应item触发断言错误。
  2. 使用联系人的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 23:01:04