方法'[]'在null上被调用。 I / flutter(接收方:空I / flutter(18112):尝试调用:[](“ id”))

时间:2020-07-06 11:53:54

标签: firebase flutter google-cloud-firestore

我试图通过单击图像从timeline_details页转到activity_feed页,但出现此错误:

I/flutter (28907): The following NoSuchMethodError was thrown building FutureBuilder<DocumentSnapshot>(dirty, state:
I/flutter (28907): _FutureBuilderState<DocumentSnapshot>#ddf5c):
I/flutter (28907): The method '[]' was called on null.
I/flutter (28907): Receiver: null
I/flutter (28907): Tried calling: []("id")
I/flutter (28907): 
I/flutter (28907): The relevant error-causing widget was:
I/flutter (28907):   FutureBuilder<DocumentSnapshot>
I/flutter (28907):   file:///F:/COURS%20DE%20PROGRAMMATION/PROJETS/MOBILE/minisocialnetwork/lib/pages/timeline_details.dart:272:12
I/flutter (28907): 
I/flutter (28907): When the exception was thrown, this was the stack:
I/flutter (28907): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
I/flutter (28907): #1      DocumentSnapshot.[] (package:cloud_firestore/src/document_snapshot.dart:31:42)
I/flutter (28907): #2      new User.fromDocument (package:minisocialnetwork/models/user.dart:26:16)
I/flutter (28907): #3      _TimelineDetailsState.buildPostHeader.<anonymous closure> (package:minisocialnetwork/pages/timeline_details.dart:278:26)
I/flutter (28907): #4      _FutureBuilderState.build (package:flutter/src/widg**

以下是参考图片:

activity_feed页面: activity_feed page

timeline_details页面: timeline_details page

这是我的完整代码:

import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:minisocialnetwork/models/user.dart';
import 'package:minisocialnetwork/pages/post_screen.dart';
import 'package:minisocialnetwork/pages/timeline.dart';
import 'package:minisocialnetwork/pages/timeline_details.dart';
import 'package:minisocialnetwork/widgets/header.dart';
import 'package:minisocialnetwork/widgets/progress.dart';
import 'package:timeago/timeago.dart' as timeago;

import 'home.dart';
import 'profile.dart';

class ActivityFeed extends StatefulWidget {
  @override
  _ActivityFeedState createState() => _ActivityFeedState();
}

class _ActivityFeedState extends State<ActivityFeed> {
  getActivityFeed() async {
    QuerySnapshot snapshot = await activityFeedRef
        .document(currentUser.id)
        .collection('feedItems')
        .orderBy('timestamp', descending: true)
        .limit(50)
        .getDocuments();
    List<ActivityFeedItem> feedItems = [];
    snapshot.documents.forEach((doc) {
      feedItems.add(ActivityFeedItem.fromDocument(doc));
      // print('Activity Feed Item: ${doc.data}');
    });
    return feedItems;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: header(context, titleText: "Notifications"),
      body: Container(
          child: FutureBuilder(
        future: getActivityFeed(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return circularProgress();
          }
          return ListView(
            children: snapshot.data,
          );
        },
      )),
    );
  }
}

Widget mediaPreview;
String activityItemText;

class ActivityFeedItem extends StatelessWidget {
  final User currentUser;
  final String profileId;
  final String username;
  final String userId;
  final String type; // 'like', 'follow', 'comment'
  final String mediaUrl;
  final String postId;
  final String title;
  final String content;
  final String category;
  final String ownerId;
  bool isLiked;
  int likeCount;
  Map likes;
  final String userProfileImg;
  final String commentData;
  final Timestamp timestamp;

  ActivityFeedItem({
    this.currentUser,
    this.profileId,
    this.username,
    this.userId,
    this.type,
    this.mediaUrl,
    this.postId,
    this.title,
    this.content,
    this.category,
    this.ownerId,
    this.isLiked,
    this.likeCount,
    this.likes,
    this.userProfileImg,
    this.commentData,
    this.timestamp,
  });

  factory ActivityFeedItem.fromDocument(DocumentSnapshot doc) {
    return ActivityFeedItem(
      username: doc['username'],
      userId: doc['userId'],
      ownerId: doc['ownerId'],
      title: doc['title'],
      content: doc['content'],
      category: doc['selectedCategory'],
      likes: doc['likes'],
      type: doc['type'],
      postId: doc['postId'],
      userProfileImg: doc['userProfileImg'],
      commentData: doc['commentData'],
      timestamp: doc['timestamp'],
      mediaUrl: doc['mediaUrl'],
    );
  }

  showTimelineDetails(BuildContext context,
      {User currentUser,
      String profileId,
      String postId,
      String ownerId,
      String userId,
      String mediaUrl,
      String username,
      String title,
      String content,
      String category,
      Map likes,
      int likeCount,
      Timestamp timestamp,
      bool isLiked}) {
    Navigator.push(context, MaterialPageRoute(builder: (context) {
      return TimelineDetails(
        currentUser: currentUser,
        profileId: profileId,
        postId: postId,
        ownerId: ownerId,
        username: username,
        title: title,
        content: content,
        category: category,
        mediaUrl: mediaUrl,
        likes: likes,
        likeCount: likeCount,
        isLiked: isLiked,
        timestamp: timestamp,
      );
    }));
  }

  configureMediaPreview(context) {
    if (type == "like" || type == 'comment') {
      mediaPreview = GestureDetector(
        onTap: () => showTimelineDetails(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 = "a aimé votre article";
    } else if (type == 'follow') {
      activityItemText = "vous suit";
    } else if (type == 'comment') {
      activityItemText = 'a écrit: $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(
          leading: GestureDetector(
            onTap: () => showProfile(context, profileId: userId),
            child: CircleAvatar(
              backgroundImage: CachedNetworkImageProvider(userProfileImg),
            ),
          ),
          title: 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',
                  ),
                ]),
          ),
          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,
      ),
    ),
  );
}

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:minisocialnetwork/models/user.dart';
import 'package:minisocialnetwork/widgets/custom_image.dart';
import 'package:minisocialnetwork/widgets/post.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:share/share.dart';

import '../widgets/progress.dart';
import '../models/user.dart';
import 'activity_feed.dart';
import 'home.dart';

//final usersRef = Firestore.instance.collection('users');

class TimelineDetails extends StatefulWidget {
  final User currentUser;
  final String profileId;
  final postId;
  final ownerId;
  final username;
  final title;
  final content;
  final category;
  final mediaUrl;
  final likes;
  final likeCount;
  bool isLiked;
  final Timestamp timestamp;

  TimelineDetails(
      {this.currentUser,
      this.profileId,
      this.postId,
      this.ownerId,
      this.username,
      this.title,
      this.content,
      this.category,
      this.mediaUrl,
      this.likes,
      this.likeCount,
      this.isLiked,
      this.timestamp});

  factory TimelineDetails.fromDocument(DocumentSnapshot doc) {
    return TimelineDetails(
      postId: doc['postId'],
      ownerId: doc['ownerId'],
      username: doc['username'],
      title: doc['title'],
      content: doc['content'],
      category: doc['selectedCategory'],
      mediaUrl: doc['mediaUrl'],
      likes: doc['likes'],
    );
  }

  int getLikeCount(likes) {
    // if no likes, return 0
    if (likes == null) {
      return 0;
    }
    int count = 0;
    // if the key is explicitly set to true, add a like
    likes.values.forEach((val) {
      if (val == true) {
        count += 1;
      }
    });
    return count;
  }

  @override
  _TimelineDetailsState createState() => _TimelineDetailsState(
        postId: this.postId,
        ownerId: this.ownerId,
        username: this.username,
        title: this.title,
        content: this.content,
        category: this.category,
        mediaUrl: this.mediaUrl,
        likes: this.likes,
        likeCount: getLikeCount(this.likes),
        timestamp: this.timestamp,
      );
}

class _TimelineDetailsState extends State<TimelineDetails> {
  final _scaffoldKey = GlobalKey<ScaffoldState>();
  final String currentUserId = currentUser?.id;
  List<Post> posts;
  bool isFollowing = false;
  bool isLoading = false;
  int postCount = 0;
  int followerCount = 0;
  int followingCount = 0;
  List<String> followingList = [];

  final String postId;
  final String ownerId;
  final String username;
  final String title;
  final String content;
  final String category;
  final String mediaUrl;
  final Timestamp timestamp;
  bool showHeart = false;
  bool isLiked = false;
  int likeCount;
  Map likes;

  _TimelineDetailsState({
    this.postId,
    this.ownerId,
    this.username,
    this.title,
    this.content,
    this.category,
    this.mediaUrl,
    this.likes,
    this.likeCount,
    this.timestamp,
  });

  @override
  void initState() {
    super.initState();
  }

  handleDeletePost(BuildContext parentContext) {
    return showDialog(
        context: parentContext,
        builder: (context) {
          return SimpleDialog(
            title: Text("Supprimer cet article?"),
            children: <Widget>[
              SimpleDialogOption(
                  onPressed: () {
                    Navigator.pop(context);
                    deletePost();
                  },
                  child: Text(
                    'Supprimer',
                    style: TextStyle(color: Colors.red, fontSize: 18.0),
                  )),
              SimpleDialogOption(
                  onPressed: () => Navigator.pop(context),
                  child: Text(
                    'Annuler',
                    style: TextStyle(fontSize: 18.0),
                  )),
            ],
          );
        });
  }

  // Note: To delete post, ownerId and currentUserId must be equal, so they can be used interchangeably
  deletePost() async {
    // delete post itself
    postsRef
        .document(ownerId)
        .collection('userPosts')
        .document(postId)
        .get()
        .then((doc) {
      if (doc.exists) {
        doc.reference.delete();
      }
    });
    // delete uploaded image for thep ost
    storageRef.child("post_$postId.jpg").delete();
    // then delete all activity feed notifications
    QuerySnapshot activityFeedSnapshot = await activityFeedRef
        .document(ownerId)
        .collection("feedItems")
        .where('postId', isEqualTo: postId)
        .getDocuments();
    activityFeedSnapshot.documents.forEach((doc) {
      if (doc.exists) {
        doc.reference.delete();
      }
    });
    // then delete all comments
    QuerySnapshot commentsSnapshot = await commentsRef
        .document(postId)
        .collection('comments')
        .getDocuments();
    commentsSnapshot.documents.forEach((doc) {
      if (doc.exists) {
        doc.reference.delete();
      }
    });
  }

  handleLikePost() {
    bool _isLiked = likes[currentUserId] == true;

    if (_isLiked) {
      postsRef
          .document(ownerId)
          .collection('userPosts')
          .document(postId)
          .updateData({'likes.$currentUserId': false});
      removeLikeFromActivityFeed();
      setState(() {
        likeCount -= 1;
        isLiked = false;
        likes[currentUserId] = false;
      });
    } else if (!_isLiked) {
      postsRef
          .document(ownerId)
          .collection('userPosts')
          .document(postId)
          .updateData({'likes.$currentUserId': true});
      addLikeToActivityFeed();
      setState(() {
        likeCount += 1;
        isLiked = true;
        likes[currentUserId] = true;
        showHeart = true;
      });
      Timer(Duration(milliseconds: 500), () {
        setState(() {
          showHeart = false;
        });
      });
    }
  }

  addLikeToActivityFeed() {
    // add a notification to the postOwner's activity feed only if comment made by OTHER user (to avoid getting notification for our own like)
    bool isNotPostOwner = currentUserId != ownerId;
    if (isNotPostOwner) {
      activityFeedRef
          .document(ownerId)
          .collection("feedItems")
          .document(postId)
          .setData({
        "type": "like",
        "username": currentUser.username,
        "userId": currentUser.id,
        "userProfileImg": currentUser.photoUrl,
        "postId": postId,
        "mediaUrl": mediaUrl,
        "timestamp": timestamp,
      });
    }
  }

  removeLikeFromActivityFeed() {
    bool isNotPostOwner = currentUserId != ownerId;
    if (isNotPostOwner) {
      activityFeedRef
          .document(ownerId)
          .collection("feedItems")
          .document(postId)
          .get()
          .then((doc) {
        if (doc.exists) {
          doc.reference.delete();
        }
      });
    }
  }

  buildPostHeader() {
    return FutureBuilder(
      future: usersRef.document(ownerId).get(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return circularProgress();
        }
        User user = User.fromDocument(snapshot.data);
        return ListView(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(
                left: 8.0,
                top: 12.0,
                right: 8.0,
                bottom: 8.0,
              ),
              child: Text(
                title,
                style: TextStyle(
                  color: Colors.black,
                  fontWeight: FontWeight.bold,
                  fontSize: 22.0,
                ),
              ),
            ),
            ListTile(
              leading: GestureDetector(
                onTap: () => showProfile(context, profileId: user.id),
                child: CircleAvatar(
                  backgroundImage: CachedNetworkImageProvider(user.photoUrl),
                  backgroundColor: Colors.grey,
                ),
              ),
              title: Padding(
                padding: const EdgeInsets.only(bottom: 8.0),
                child: Row(
                  children: <Widget>[
                    Padding(
                      padding: const EdgeInsets.only(right: 8.0),
                      child: Text(
                        username,
                      ),
                    ),
//                     Container(
//                       width: 100,
//                       height: 27,
//                       child:  buildProfileButton(),
//                     ),
                  ],
                ),
              ),
              subtitle: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  Text(
                    timeago.format(timestamp.toDate(), locale: 'fr'),
                    overflow: TextOverflow.ellipsis,
                  ),
                  Text(category),
                ],
              ),
              // trailing: IconButton(
              //   onPressed: () => handleDeletePost(context),
              //   icon: Icon(Icons.more_vert),
              // ),
            ),
            Container(child: cachedNetworkImage(mediaUrl)),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(content, style: TextStyle(fontSize: 18.0)),
            ),
          ],
        );
      },
    );
  }

  void goBack() {
    Navigator.pop(context);
  }

  @override
  Widget build(context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        leading: GestureDetector(
            onTap: goBack,
            child: Icon(
              Icons.arrow_back,
              color: Colors.white,
            )),
        title: Text(
          'MiaKoz',
          style: TextStyle(color: Colors.white),
        ),
      ),
      body: Container(child: buildPostHeader()),
      bottomNavigationBar: BottomAppBar(
        color: Colors.white,
        child: Padding(
          padding: const EdgeInsets.only(left: 8.0, right: 8.0),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              Row(
                children: <Widget>[
                  GestureDetector(
                    onTap: handleLikePost,
                    child: Padding(
                      padding: const EdgeInsets.only(left: 8.0),
                      child: Image(
                        image: isLiked
                            ? AssetImage("assets/images/clap.png")
                            : AssetImage("assets/images/no_clap.png"),
                        color: Colors.cyan,
                        fit: BoxFit.scaleDown,
                        alignment: Alignment.center,
                        width: 28,
                        height: 28,
                      ),
                    ),
                  ),
//                        GestureDetector(
//                          onTap: handleLikePost,
//                          child: Icon(
//                            isLiked ? Icons.favorite : Icons.favorite_border,
//                            size: 28.0,
//                            color: Colors.cyan,
//                          ),
//                        ),
                  Container(
                    margin: EdgeInsets.only(left: 8.0),
                    child: Text(
                      "$likeCount claps",
                      style: TextStyle(
                        color: Colors.black,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                ],
              ),
              GestureDetector(
                onTap: () => showComments(
                  context,
                  postId: postId,
                  ownerId: ownerId,
                  mediaUrl: mediaUrl,
                ),
                child: Icon(
                  Icons.chat,
                  size: 28.0,
                  color: Colors.cyan,
                ),
              ),
              IconButton(
                  icon: Icon(
                    Icons.share,
                    color: Theme.of(context).primaryColor,
                  ),
                  onPressed: () {
                    final RenderBox box = context.findRenderObject();
                    Share.share('${title} - ${mediaUrl}',
                        subject: content,
                        sharePositionOrigin:
                            box.localToGlobal(Offset.zero) & box.size);
                  }),
            ],
          ),
        ),
      ),
    );
  }
}

0 个答案:

没有答案