首页 > 解决方案 > 断言失败:使用 Contact() 类时,布尔表达式在 Flutter 中不得为空

问题描述

isSelected我在文件中定义了一个 bool 类型的变量contacts_service.dart(我从这里导入)。我自己编辑的,我编辑的行是 - 159, 186, 195, 239 in contacts_service.dart.

现在我在我的页面 - 中使用这个类create_group.dart,并在new_contact_card.dart. 我也做了一个变量isSelectednew_contact_card.dart我在那个文件中进一步使用了。现在的问题是,我遇到了一个例外 - boolean expression must not be null

一个重要的说明 - 我初始化isSelected为假,无论是在new_contact_card.dart还是contacts_service.dart。我正在通过使用onTapof来改变它的价值InkWell()。但它仍然说我称它为空。

这是contacts_service.dart-

import 'dart:async';
import 'dart:typed_data';

import 'package:collection/collection.dart';
import 'package:flutter/services.dart';
import 'package:quiver/core.dart';

export 'share.dart';

class ContactsService {
  static const MethodChannel _channel =
      MethodChannel('github.com/clovisnicolas/flutter_contacts');

  /// Fetches all contacts, or when specified, the contacts with a name
  /// matching [query]
  static Future<Iterable<Contact>> getContacts(
      {String query,
      bool withThumbnails = true,
      bool photoHighResolution = true,
      bool orderByGivenName = true,
      bool iOSLocalizedLabels = true}) async {
    Iterable contacts =
        await _channel.invokeMethod('getContacts', <String, dynamic>{
      'query': query,
      'withThumbnails': withThumbnails,
      'photoHighResolution': photoHighResolution,
      'orderByGivenName': orderByGivenName,
      'iOSLocalizedLabels': iOSLocalizedLabels,
    });
    return contacts.map((m) => Contact.fromMap(m));
  }

  /// Fetches all contacts, or when specified, the contacts with the phone
  /// matching [phone]
  static Future<Iterable<Contact>> getContactsForPhone(String phone,
      {bool withThumbnails = true,
      bool photoHighResolution = true,
      bool orderByGivenName = true,
      bool iOSLocalizedLabels = true}) async {
    if (phone == null || phone.isEmpty) return Iterable.empty();

    Iterable contacts =
        await _channel.invokeMethod('getContactsForPhone', <String, dynamic>{
      'phone': phone,
      'withThumbnails': withThumbnails,
      'photoHighResolution': photoHighResolution,
      'orderByGivenName': orderByGivenName,
      'iOSLocalizedLabels': iOSLocalizedLabels,
    });
    return contacts.map((m) => Contact.fromMap(m));
  }

  /// Fetches all contacts, or when specified, the contacts with the email
  /// matching [email]
  /// Works only on iOS
  static Future<Iterable<Contact>> getContactsForEmail(String email,
      {bool withThumbnails = true,
        bool photoHighResolution = true,
        bool orderByGivenName = true,
        bool iOSLocalizedLabels = true}) async {
    Iterable contacts = await _channel.invokeMethod('getContactsForEmail',<String,dynamic>{
      'email': email,
      'withThumbnails': withThumbnails,
      'photoHighResolution': photoHighResolution,
      'orderByGivenName': orderByGivenName,
      'iOSLocalizedLabels': iOSLocalizedLabels,
    });
    return contacts.map((m) => Contact.fromMap(m));
  }

  /// Loads the avatar for the given contact and returns it. If the user does
  /// not have an avatar, then `null` is returned in that slot. Only implemented
  /// on Android.
  static Future<Uint8List> getAvatar(
      final Contact contact, {final bool photoHighRes = true}) =>
      _channel.invokeMethod('getAvatar', <String, dynamic>{
        'contact': Contact._toMap(contact),
        'photoHighResolution': photoHighRes,
      });

  /// Adds the [contact] to the device contact list
  static Future addContact(Contact contact) =>
      _channel.invokeMethod('addContact', Contact._toMap(contact));

  /// Deletes the [contact] if it has a valid identifier
  static Future deleteContact(Contact contact) =>
      _channel.invokeMethod('deleteContact', Contact._toMap(contact));

  /// Updates the [contact] if it has a valid identifier
  static Future updateContact(Contact contact) =>
      _channel.invokeMethod('updateContact', Contact._toMap(contact));

  static Future<Contact> openContactForm({bool iOSLocalizedLabels = true}) async {
    dynamic result = await _channel.invokeMethod('openContactForm',<String,dynamic>{
      'iOSLocalizedLabels': iOSLocalizedLabels,
    });
   return _handleFormOperation(result);
  }

  static Future<Contact> openExistingContact(Contact contact, {bool iOSLocalizedLabels = true}) async {
   dynamic result = await _channel.invokeMethod('openExistingContact',<String,dynamic>{
     'contact': Contact._toMap(contact),
     'iOSLocalizedLabels': iOSLocalizedLabels,
   }, );
   return _handleFormOperation(result);
  }

  // Displays the device/native contact picker dialog and returns the contact selected by the user
  static Future<Contact> openDeviceContactPicker({bool iOSLocalizedLabels = true}) async {
    dynamic result = await _channel.invokeMethod('openDeviceContactPicker',<String,dynamic>{
      'iOSLocalizedLabels': iOSLocalizedLabels,
    });
    // result contains either :
    // - an Iterable of contacts containing 0 or 1 contact
    // - a FormOperationErrorCode value
    if (result is Iterable) {
      if (result.isEmpty) {
        return null;
      }
      result = result.first;
    }
    return _handleFormOperation(result);
  }

  static Contact _handleFormOperation(dynamic result) {
    if(result is int) {
      switch (result) {
        case 1:
          throw FormOperationException(errorCode: FormOperationErrorCode.FORM_OPERATION_CANCELED);
        case 2:
          throw FormOperationException(errorCode: FormOperationErrorCode.FORM_COULD_NOT_BE_OPEN);
        default:
          throw FormOperationException(errorCode: FormOperationErrorCode.FORM_OPERATION_UNKNOWN_ERROR);
      }
    } else if(result is Map) {
      return Contact.fromMap(result);
    } else {
      throw FormOperationException(errorCode: FormOperationErrorCode.FORM_OPERATION_UNKNOWN_ERROR);
    }
  }
}

class FormOperationException implements Exception {
  final FormOperationErrorCode errorCode;

  const FormOperationException({this.errorCode});
   String toString() => 'FormOperationException: $errorCode';
}

enum FormOperationErrorCode {
  FORM_OPERATION_CANCELED,
  FORM_COULD_NOT_BE_OPEN,
  FORM_OPERATION_UNKNOWN_ERROR
}


class Contact {
  Contact({
    this.isSelected=false,
    this.displayName,
    this.givenName,
    this.middleName,
    this.prefix,
    this.suffix,
    this.familyName,
    this.company,
    this.jobTitle,
    this.emails,
    this.phones,
    this.postalAddresses,
    this.avatar,
    this.birthday,
    this.androidAccountType,
    this.androidAccountTypeRaw,
    this.androidAccountName,
  });

  String identifier, displayName, givenName, middleName, prefix, suffix, familyName, company, jobTitle;
  String androidAccountTypeRaw, androidAccountName;
  AndroidAccountType androidAccountType;
  Iterable<Item> emails = [];
  Iterable<Item> phones = [];
  Iterable<PostalAddress> postalAddresses = [];
  Uint8List avatar;
  DateTime birthday;
  bool isSelected;

  String initials() {
    return ((this.givenName?.isNotEmpty == true ? this.givenName[0] : "") +
            (this.familyName?.isNotEmpty == true ? this.familyName[0] : ""))
        .toUpperCase();
  }

  Contact.fromMap(Map m) {
    isSelected = m["isSelected"];
    identifier = m["identifier"];
    displayName = m["displayName"];
    givenName = m["givenName"];
    middleName = m["middleName"];
    familyName = m["familyName"];
    prefix = m["prefix"];
    suffix = m["suffix"];
    company = m["company"];
    jobTitle = m["jobTitle"];
    androidAccountTypeRaw = m["androidAccountType"];
    androidAccountType = accountTypeFromString(androidAccountTypeRaw);
    androidAccountName = m["androidAccountName"];
    emails = (m["emails"] as Iterable)?.map((m) => Item.fromMap(m));
    phones = (m["phones"] as Iterable)?.map((m) => Item.fromMap(m));
    postalAddresses = (m["postalAddresses"] as Iterable)
        ?.map((m) => PostalAddress.fromMap(m));
    avatar = m["avatar"];
    try {
      birthday = DateTime.parse(m["birthday"]);
    } catch (e) {
      birthday = null;
    }
  }

  static Map _toMap(Contact contact) {
    var emails = [];
    for (Item email in contact.emails ?? []) {
      emails.add(Item._toMap(email));
    }
    var phones = [];
    for (Item phone in contact.phones ?? []) {
      phones.add(Item._toMap(phone));
    }
    var postalAddresses = [];
    for (PostalAddress address in contact.postalAddresses ?? []) {
      postalAddresses.add(PostalAddress._toMap(address));
    }

    final birthday = contact.birthday == null
        ? null
        : "${contact.birthday.year.toString()}-${contact.birthday.month.toString().padLeft(2, '0')}-${contact.birthday.day.toString().padLeft(2, '0')}";

    return {
      "isSelected": contact.isSelected,
      "identifier": contact.identifier,
      "displayName": contact.displayName,
      "givenName": contact.givenName,
      "middleName": contact.middleName,
      "familyName": contact.familyName,
      "prefix": contact.prefix,
      "suffix": contact.suffix,
      "company": contact.company,
      "jobTitle": contact.jobTitle,
      "androidAccountType": contact.androidAccountTypeRaw,
      "androidAccountName": contact.androidAccountName,
      "emails": emails,
      "phones": phones,
      "postalAddresses": postalAddresses,
      "avatar": contact.avatar,
      "birthday": birthday
    };
  }

  Map toMap() {
    return Contact._toMap(this);
  }

  /// The [+] operator fills in this contact's empty fields with the fields from [other]
  operator +(Contact other) => Contact(
      givenName: this.givenName ?? other.givenName,
      middleName: this.middleName ?? other.middleName,
      prefix: this.prefix ?? other.prefix,
      suffix: this.suffix ?? other.suffix,
      familyName: this.familyName ?? other.familyName,
      company: this.company ?? other.company,
      jobTitle: this.jobTitle ?? other.jobTitle,
      androidAccountType: this.androidAccountType ?? other.androidAccountType,
      androidAccountName: this.androidAccountName ?? other.androidAccountName,
      emails: this.emails == null
          ? other.emails
          : this.emails.toSet().union(other.emails?.toSet() ?? Set()).toList(),
      phones: this.phones == null
          ? other.phones
          : this.phones.toSet().union(other.phones?.toSet() ?? Set()).toList(),
      postalAddresses: this.postalAddresses == null
          ? other.postalAddresses
          : this
              .postalAddresses
              .toSet()
              .union(other.postalAddresses?.toSet() ?? Set())
              .toList(),
      avatar: this.avatar ?? other.avatar,
      birthday: this.birthday ?? other.birthday,
    );

  /// Returns true if all items in this contact are identical.
  @override
  bool operator ==(Object other) {
    return other is Contact &&
        this.avatar == other.avatar &&
        this.company == other.company &&
        this.displayName == other.displayName &&
        this.givenName == other.givenName &&
        this.familyName == other.familyName &&
        this.identifier == other.identifier &&
        this.jobTitle == other.jobTitle &&
        this.androidAccountType == other.androidAccountType &&
        this.androidAccountName == other.androidAccountName &&
        this.middleName == other.middleName &&
        this.prefix == other.prefix &&
        this.suffix == other.suffix &&
        this.birthday == other.birthday &&
        DeepCollectionEquality.unordered().equals(this.phones, other.phones) &&
        DeepCollectionEquality.unordered().equals(this.emails, other.emails) &&
        DeepCollectionEquality.unordered()
            .equals(this.postalAddresses, other.postalAddresses);
  }

  @override
  int get hashCode {
    return hashObjects([
      this.company,
      this.displayName,
      this.familyName,
      this.givenName,
      this.identifier,
      this.jobTitle,
      this.androidAccountType,
      this.androidAccountName,
      this.middleName,
      this.prefix,
      this.suffix,
      this.birthday,
    ].where((s) => s != null));
  }

  AndroidAccountType accountTypeFromString(String androidAccountType) {
    if (androidAccountType == null) {
      return null;
    }
    if (androidAccountType.startsWith("com.google")) {
      return AndroidAccountType.google;
    } else if (androidAccountType.startsWith("com.whatsapp")) {
      return AndroidAccountType.whatsapp;
    } else if (androidAccountType.startsWith("com.facebook")) {
      return AndroidAccountType.facebook;
    }
    /// Other account types are not supported on Android
    /// such as Samsung, htc etc...
    return AndroidAccountType.other;
  }
}

class PostalAddress {
  PostalAddress(
      {this.label,
      this.street,
      this.city,
      this.postcode,
      this.region,
      this.country});
  String label, street, city, postcode, region, country;

  PostalAddress.fromMap(Map m) {
    label = m["label"];
    street = m["street"];
    city = m["city"];
    postcode = m["postcode"];
    region = m["region"];
    country = m["country"];
  }

  @override
  bool operator ==(Object other) {
    return other is PostalAddress &&
        this.city == other.city &&
        this.country == other.country &&
        this.label == other.label &&
        this.postcode == other.postcode &&
        this.region == other.region &&
        this.street == other.street;
  }

  @override
  int get hashCode {
    return hashObjects([
      this.label,
      this.street,
      this.city,
      this.country,
      this.region,
      this.postcode,
    ].where((s) => s != null));
  }

  static Map _toMap(PostalAddress address) => {
        "label": address.label,
        "street": address.street,
        "city": address.city,
        "postcode": address.postcode,
        "region": address.region,
        "country": address.country
      };

  @override
  String toString() {
    String finalString = "";
    if (this.street != null) {
      finalString += this.street;
    }
    if (this.city != null) {
      if (finalString.isNotEmpty) {
        finalString += ", " + this.city;
      } else {
        finalString += this.city;
      }
    }
    if (this.region != null) {
      if (finalString.isNotEmpty) {
        finalString += ", " + this.region;
      } else {
        finalString += this.region;
      }
    }
    if (this.postcode != null) {
      if (finalString.isNotEmpty) {
        finalString += " " + this.postcode;
      } else {
        finalString += this.postcode;
      }
    }
    if (this.country != null) {
      if (finalString.isNotEmpty) {
        finalString += ", " + this.country;
      } else {
        finalString += this.country;
      }
    }
    return finalString;
  }
}

/// Item class used for contact fields which only have a [label] and
/// a [value], such as emails and phone numbers
class Item {
  Item({this.label, this.value});

  String label, value;

  Item.fromMap(Map m) {
    label = m["label"];
    value = m["value"];
  }

  @override
  bool operator ==(Object other) {
    return other is Item &&
        this.label == other.label &&
        this.value == other.value;
  }

  @override
  int get hashCode => hash2(label ?? "", value ?? "");

  static Map _toMap(Item i) => {"label": i.label, "value": i.value};
}

enum AndroidAccountType {
  facebook,
  google,
  whatsapp,
  other
}

这里是create_group.dart——

import 'package:flutter/material.dart';
import 'package:contacts_service/contacts_service.dart';
import 'package:flutter_whatsapp/Widgets/new_contact_card.dart';
import 'package:flutter_whatsapp/Widgets/specific_card.dart';

class CreateGroup extends StatefulWidget {
  @override
  _CreateGroupState createState() => _CreateGroupState();
}

class _CreateGroupState extends State<CreateGroup> {

  Iterable<Contact> _contacts = [];
  List<Contact> groups = [];

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getContacts();
  }

  Future<void> getContacts() async {
    final Iterable<Contact> contacts = await ContactsService.getContacts();
    setState(() {
      _contacts=contacts;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('New group'),
            Text(
              'Add participants',
              style: TextStyle(
                fontSize: 10.0,
              ),
            )
          ],
        ),
        actions: [
          IconButton(
            icon: Icon(
              Icons.search,
            ),
            onPressed: (){},
          ),
        ],
      ),
      body: _contacts != null
          ? ListView.builder(
        itemCount: _contacts.length ?? 0,
        itemBuilder: (BuildContext context, int index) {
          Contact contact = _contacts?.elementAt(index);
          return InkWell(
            onTap: () {
              setState(() {
                if(contact.isSelected) {
                  contact.isSelected=false;
                  groups.add(contact);
                }
                else {
                  contact.isSelected=true;
                  groups.remove(contact);
                }
              });
            },
            child: NewContactCard(
              isSelected: contact?.isSelected,
              contact: contact,
              uint8list: contact.avatar,
              text: contact.initials(),
              color: Theme.of(context).accentColor,
              name: contact.displayName,
            ),
          );
        },
      ) : Center(child: CircularProgressIndicator(),),
    );
  }
}

这是代码 - new_contact_card.dart-

import 'dart:typed_data';

import 'package:contacts_service/contacts_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';

class NewContactCard extends StatefulWidget {

  Uint8List uint8list;
  String text, name;
  Color color;
  bool isSelected=false;
  Contact contact;

  NewContactCard({this.uint8list, this.color, this.text, this.name, this.contact, this.isSelected});

  @override
  _NewContactCardState createState() => _NewContactCardState();
}

class _NewContactCardState extends State<NewContactCard> {
  @override
  Widget build(BuildContext context) {
    return ListTile(
      contentPadding:
      EdgeInsets.symmetric(vertical: 2, horizontal: 18),
      leading: Container(
        height: MediaQuery.of(context).size.width*0.115,
        width: MediaQuery.of(context).size.width*0.125,
        child: Stack(
          children: [
            (widget.uint8list != null && widget.uint8list.isNotEmpty)
                ? CircleAvatar(
              backgroundImage: MemoryImage(widget.uint8list),
              backgroundColor: Colors.grey[400],
              radius: MediaQuery.of(context).size.width*0.05,
              child: SvgPicture.asset("assets/person.svg", color: Colors.white,),
            )
                : CircleAvatar(
              backgroundColor: Colors.grey[400],
              radius: MediaQuery.of(context).size.width*0.05,
              child: SvgPicture.asset("assets/person.svg", color: Colors.white,),
            ),
            widget.isSelected ? Positioned(
              right: MediaQuery.of(context).size.width*0.005,
              bottom: MediaQuery.of(context).size.width*0.008,
              child: CircleAvatar(
                radius: MediaQuery.of(context).size.width*0.025,
                backgroundColor: Color(0xFF075E54),
                child: Icon(
                  Icons.check,
                  color: Colors.white,
                  size: MediaQuery.of(context).size.width*0.04,
                ),
              ),
            ) : Container(),
          ],
        ),
      ),
      title: Text(
        widget.name ?? 0,
        style: TextStyle(
          color: Colors.black,
          fontWeight: FontWeight.w600,
          fontSize: MediaQuery.of(context).size.width*0.042,
        ),
      ),
      subtitle: Text(
        "This will be done later",
        style: TextStyle(
          color: Colors.grey[600],
          fontSize: MediaQuery.of(context).size.width*0.032,
        ),
      ),
    );;
  }
}

这就是错误 -

The following assertion was thrown building NewContactCard(dirty, dependencies: [MediaQuery], state: _NewContactCardState#2e664):
Failed assertion: boolean expression must not be null

The relevant error-causing widget was: 
  NewContactCard file:///C:/Users/Hp/AndroidStudioProjects/flutter_whatsapp/lib/Pages/create_group.dart:75:20
When the exception was thrown, this was the stack: 
#0      _NewContactCardState.build (package:flutter_whatsapp/Widgets/new_contact_card.dart:44:20)
#1      StatefulElement.build (package:flutter/src/widgets/framework.dart:4612:27)
#2      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4495:15)
#3      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4667:11)
#4      Element.rebuild (package:flutter/src/widgets/framework.dart:4189:5)
...

注意 - 我在代码中其他地方的(或)isSelected字段中使用了 false 值,并且效果很好,我认为返回的值在这里给出了问题。new_contact_card.dartNewContactCard()Contact()

标签: androidiosflutterdartexception

解决方案


您正在NewContactCard以这种方式创建:

child: NewContactCard(
  isSelected: contact?.isSelected,
  contact: contact,
  uint8list: contact.avatar,
  text: contact.initials(),
  color: Theme.of(context).accentColor,
  name: contact.displayName,
),

如果contactnull你没有得到isSelected它(因为contact?.isSelected返回null)。isSelected在这种情况下,您需要通过以下方式更改您的声明:

child: NewContactCard(
  isSelected: contact?.isSelected ?? false,
  contact: contact,
  uint8list: contact.avatar,
  text: contact.initials(),
  color: Theme.of(context).accentColor,
  name: contact.displayName,
),

推荐阅读