#flutter #flutter-listview #flutter-futurebuilder #dart-null-safety
Вопрос:
После перехода на нулевую безопасность я получаю ошибку в ListView как «Тип аргумента» Объект? «не может быть присвоен типу параметра «Список»». Я получаю сообщение об return ListView(children: snapshot.data,);
ошибке, Может ли кто-нибудь помочь мне исправить эту ошибку и создать представление списка для activityfeeditem в моем приложении?
Вот мой код для activity_feed.dart,
class ActivityFeed extends StatefulWidget {
@override
_ActivityFeedState createState() => _ActivityFeedState();
}
class _ActivityFeedState extends State<ActivityFeed> {
getActivityFeed() async {
QuerySnapshot snapshot = await activityFeedRef
.doc(currentUser!.id)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.get();
List<ActivityFeedItem> feedItems = [];
snapshot.docs.forEach((doc) {
feedItems.add(ActivityFeedItem.fromDocument(doc));
print('Activity Feed Item: ${doc.data}');
});
return feedItems;
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.deepPurple[50],
appBar: header(context, titleText: "Activity Feed"),
body: Container(
child: FutureBuilder(
future: getActivityFeed(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
return ListView(
children: snapshot.data,
// Here I'm getting error on `snapshot.data`
);
},
),
),
);
}
}
Widget? mediaPreview;
String? activityItemText;
class ActivityFeedItem extends StatelessWidget {
final String? username;
final String? userId;
final String? type; // 'like', 'follow', 'comment'
final String? mediaUrl;
final String? postId;
final String? userProfileImg;
final String? commentData;
final Timestamp? timestamp;
ActivityFeedItem({
this.username,
this.userId,
this.type,
this.mediaUrl,
this.postId,
this.userProfileImg,
this.commentData,
this.timestamp,
});
factory ActivityFeedItem.fromDocument(DocumentSnapshot doc) {
return ActivityFeedItem(
username: doc['username'],
userId: doc['userId'],
type: doc['type'],
postId: doc['postId'],
userProfileImg: doc['userProfileImg'],
commentData: doc['commentData'],
timestamp: doc['timestamp'],
mediaUrl: doc['mediaUrl'],
);
}
showPost(context) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PostScreen(postId: postId, userId: userId)));
}
configureMediaPreview(context) {
if (type == "like" || type == 'comment') {
mediaPreview = GestureDetector(
onTap: () => showPost(context),
child: Container(
height: 50.0,
width: 50.0,
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(mediaUrl!),
),
),
)),
),
);
} else {
mediaPreview = Text('');
}
if (type == 'like') {
activityItemText = "liked your post";
} else if (type == 'follow') {
activityItemText = "is following you";
} else if (type == 'comment') {
activityItemText = 'replied: $commentData';
} else {
activityItemText = "Error: Unknown type '$type'";
}
}
@override
Widget build(BuildContext context) {
configureMediaPreview(context);
return Padding(
padding: EdgeInsets.only(bottom: 2.0),
child: Container(
color: Colors.white54,
child: ListTile(
title: GestureDetector(
onTap: () => showProfile(context, profileId: userId),
child: RichText(
overflow: TextOverflow.ellipsis,
text: TextSpan(
style: TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: [
TextSpan(
text: username,
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text: ' $activityItemText',
),
]),
),
),
leading: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(userProfileImg!),
),
subtitle: Text(
timeago.format(timestamp!.toDate()),
overflow: TextOverflow.ellipsis,
),
trailing: mediaPreview,
),
),
);
}
}
showProfile(BuildContext context, {String? profileId}) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Profile(
profileId: profileId,
),
),
);
}
Я перепробовал много способов, но я не могу понять, как я могу это исправить
Новый код для списка
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.deepPurple[50],
appBar: header(context, titleText: "Activity Feed"),
body: Container(
child: StreamBuilder<QuerySnapshot>(
stream: activityFeedRef
.doc(currentUser!.id)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: circularProgress(),
);
} else
return ListView(
children: snapshot.data!.docs.map((doc) {
return Card(
child: ListTile(
title: GestureDetector(
onTap: () =>
showProfile(context, profileId: doc['userId']),
child: RichText(
overflow: TextOverflow.ellipsis,
text: TextSpan(
style: TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: [
TextSpan(
text: doc['username'],
style: TextStyle(
fontWeight: FontWeight.bold),
),
TextSpan(
text: ' $activityItemText',
),
]),
),
),
leading: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
doc['userProfileImg']!),
),
subtitle: Text(
timeago.format(doc['timestamp']!.toDate()),
overflow: TextOverflow.ellipsis,
),
trailing: mediaPreview,
),
);
}).toList(),
);
}),
));
}
Ответ №1:
Чейдж твой getActivityFeed
Future<List<ActivityFeedItem>> getActivityFeed() async {
try{
QuerySnapshot snapshot = await activityFeedRef
.doc(currentUser!.id)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.get();
List<ActivityFeedItem> feedItems = [];
snapshot.docs.forEach((doc) {
feedItems.add(ActivityFeedItem.fromDocument(doc));
print('Activity Feed Item: ${doc.data}');
});
return feedItems;
}
catch (error) {
print(error);
return <ActivityFeedItem>[];
}}
измените вас FutureBuilder
следующим образом
FutureBuilder<List<ActivityFeedItem>>(
future: getActivityFeed(),
builder: (BuildContextcontext, AsyncSnapshot<List<ActivityFeedItem>> snapshot) {
if (snapshot.hasError){
return Center(child: Text("You have an error in loading
data"));
}
if (snapshot.hasData) {
return ListView(
children: snapshot.data!,
);
}
return CirclularProgressIndicator();
Комментарии:
1. получение ошибки в качестве типа аргумента «Список<ActivityFeedItem>?» не может быть присвоен типу параметра » Список<ActivityFeedItem><Виджет>».
2. Спасибо, но все равно он не работает, даже несмотря на то, что он не показывает ошибок при запуске приложения, эта страница продолжает показывать циклический прогресс и не загружает никаких элементов, на консоли отладки он отображается как элемент ленты активности: Закрытие: () => Карта><Строка, динамическая> из функции «данные»:.
3. Вы уверены, что возвращаетесь
ListView
, когдаsnapsot.hasData
это правда.4. @MaldivesHunt Можете ли вы добавить
print(snapshot.data)
передif (snapshot.hasData)
тем, чтобы посмотреть, что там напечатано?5. Это означает, что ваша
getActivityFeed()
функция либо возвращает значение null, либо выдает ошибку (скорее всего). Выводится ли «Элемент ленты активности: …» в вашей консоли?
Ответ №2:
Вы также можете использовать as
.
ListView(
children: object as List<Widget>,
)
Ответ №3:
Я вижу, что вы используете StreamBuilder вместо FutureBuilder, но, как бы то ни было, я считаю, что нашел решение исходной проблемы FutureBuilder.
Прежде всего: Используя следующие инструкции печати, вы можете лучше устранить проблему, я обнаружил, что запрашивал пути, которых не существовало, с комбинациями неправильных .doc(идентификатор пользователя) и .doc(идентификатор владельца) в сообщениях.таким образом, процесс десериализации не работал правильно для меня при переключении между пользователями во время отладки (использовал пакет поставщика, чтобы в конечном итоге исправить это), но приведенные ниже инструкции по печати помогли мне определить некоторые проблемы, которые у меня были (которые могли или не могли способствовать возникновению проблемы для вас, но стоит посмотреть).
getActivityFeed() async {
QuerySnapshot snapshot = await FirebaseFirestore.instance
.collection('feed')
.doc(_auth.currentUser!.uid)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.get();
List<ActivityFeedItem> feedItems = [];
snapshot.docs.forEach((doc) {
feedItems.add(ActivityFeedItem.fromDocument(doc));
print('Activity Feed Item: ${doc.id}');
print('Activity Feed Item: ${doc.data()}');
});
// return feedItems;
return snapshot.docs;
Затем я обнаружил, что процесс десериализации работает неправильно из-за разницы между «лайками» и «комментариями», из-за того, что «лайки» имеют 7 входов, а «комментарии» имеют 8 входов. Комментарии содержат дополнительный ввод «commentData», который я настроил вручную для «лайков» в части addLikeToActivityFeed() и установил его в пустую строку как таковую:
'commentData': '',
Наконец, я добавил динамический тип в FutureBuilder, чтобы избавиться от => тип аргумента «Объект?» не может быть присвоен ошибке типа параметра «Список»…
Container(
child: FutureBuilder<dynamic>(
future: getActivityFeed(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
return ListView(
children: snapshot.data,
);
},
),
),