#android #ios #flutter #dart #exception
#Android #iOS #flutter #dart #исключение
Вопрос:
Я определил переменную isSelected
типа bool в contacts_service.dart
файле (который я импортировал отсюда). Я отредактировал его сам, и строки, которые я редактировал, были — 159, 186, 195, 239 дюймов contacts_service.dart
.
Теперь я использую этот класс на своей странице — create_group.dart
и использую значение этого класса в new_contact_card.dart
. Я isSelected
тоже создал переменную, new_contact_card.dart
которую я использовал в дальнейшем в этом файле. Теперь проблема в том, что я получаю исключение … boolean expression must not be null
.
Важное замечание — я инициализировал isSelected
как false, как в new_contact_card.dart
, так и contacts_service.dart
в too . И я меняю его значение с помощью onTap
of InkWell()
. Но все равно там написано, что я вызвал его на null.
Вот это самое 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 amp;amp;
this.avatar == other.avatar amp;amp;
this.company == other.company amp;amp;
this.displayName == other.displayName amp;amp;
this.givenName == other.givenName amp;amp;
this.familyName == other.familyName amp;amp;
this.identifier == other.identifier amp;amp;
this.jobTitle == other.jobTitle amp;amp;
this.androidAccountType == other.androidAccountType amp;amp;
this.androidAccountName == other.androidAccountName amp;amp;
this.middleName == other.middleName amp;amp;
this.prefix == other.prefix amp;amp;
this.suffix == other.suffix amp;amp;
this.birthday == other.birthday amp;amp;
DeepCollectionEquality.unordered().equals(this.phones, other.phones) amp;amp;
DeepCollectionEquality.unordered().equals(this.emails, other.emails) amp;amp;
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 amp;amp;
this.city == other.city amp;amp;
this.country == other.country amp;amp;
this.label == other.label amp;amp;
this.postcode == other.postcode amp;amp;
this.region == other.region amp;amp;
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 amp;amp;
this.label == other.label amp;amp;
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 amp;amp; 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)
...
Примечание — я использовал значение false в isSelected
поле new_contact_card.dart
(или NewContactCard()
) где-то еще в моем коде, и это сработало нормально, я думаю, что значение, возвращаемое из Contact()
, создает проблему здесь.
Ответ №1:
Вы создаете NewContactCard
таким образом:
child: NewContactCard(
isSelected: contact?.isSelected,
contact: contact,
uint8list: contact.avatar,
text: contact.initials(),
color: Theme.of(context).accentColor,
name: contact.displayName,
),
Если contact
null
вы не получаете 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,
),