首页 > 解决方案 > 如何映射用户特定的喜欢/收藏夹?类似于大多数社交应用上的点赞功能

问题描述

您好,我是一名自学 Flutter 开发人员,正在编写我的第一个应用程序。我正在 Flutter 中构建一个基本的报价应用程序,它利用 Firebase 进行身份验证,并将 FireStore 作为数据库。这是 FireStore noSQL 架构:

有一个 Stories 集合,它是提供图像链接和文本的只读数据,如下所示。请注意,文档 ID 是在 Stories 集合中自动生成的:

故事集

然后是一个 Users 集合,用于存储用户数据,例如用户名、电子邮件、帐户创建日期时间和一个喜欢数组 [可能存储喜欢的故事]。在此集合中,文档的 ID 是经过身份验证(登录)的用户的 UID(唯一 ID)。

用户收藏

所以这是我正在追逐的用户故事和我的方法:

每当用户点击下面最喜欢的图标时,必须将喜欢的内容保存到用户集合中,其中用户的 uid 是文档 ID,并将喜欢的故事存储在 likes 数组中。

模拟器之前的 UI 截图

然后使用 if 语句说,如果经过身份验证的用户在 likes 数组中有故事,则将勾勒出的白色心脏变成红色心脏,如下所示:

点赞后的模拟器截图

但是,我的代码中有一个错误,当用户点击最喜欢的图标时,它会立即将所有故事的心变成红色。有人可以帮忙吗?这是代码片段:

class FirestoreSlideshowState extends State<FirestoreSlideshow> {
  static const likedKey = 'liked_key';

  bool liked;

  final PageController ctrl = PageController(viewportFraction: 0.8);

  final Firestore db = Firestore.instance;
  Stream slides;

  String activeTag = 'favs';

  // Keep track of current page to avoid unnecessary renders

  int currentPage = 0;


@override

  void initState() {

    super.initState();
    _restorePersistedPreference();
    _queryDb();

    // Set state when page changes
    ctrl.addListener(() {
      int next = ctrl.page.round();

      if (currentPage != next) {
        setState(() {
          currentPage = next;
        });
      }
    });
  }

  void _restorePersistedPreference() async {
    var preferences = await SharedPreferences.getInstance();
    var liked = preferences.getBool(likedKey) ?? false;
    setState(() {
      this.liked = liked;
    });
  }

  void _persistPreference() async {
    setState(() {
      liked = !liked;
    });
    var preferences = await SharedPreferences.getInstance();
    preferences.setBool(likedKey, liked);
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
        stream: slides,
        initialData: [],
        builder: (context, AsyncSnapshot snap) {
          List slideList = snap.data.toList();

          return PageView.builder(
              controller: ctrl,
              itemCount: slideList.length,
              itemBuilder: (context, int currentIdx) {
                if (slideList.length >= currentIdx) {
                  // Active page
                  bool active = currentIdx == currentPage;
                  return _buildStoryPage(slideList[currentIdx], active);
                }
              });
        });
  }

  Stream _queryDb({String tag = 'favs'}) {
    // Make a Query
    Query query = db.collection('Stories').where('tags', arrayContains: tag);

    // Map the documents to the data payload
    slides =
        query.snapshots().map((list) => list.documents.map((doc) => doc.data));

    // Update the active tag
    setState(() {
      activeTag = tag;
    });
  }

  _buildStoryPage(Map data, bool active) {

    final _width = MediaQuery.of(context).size.width;
    final _height = MediaQuery.of(context).size.height;
    // Animated Properties
    final double blur = active ? 20 : 0;
    final double offset = active ? 20 : 0;
    final double top = active ? 75 : 150;

    return AnimatedContainer(
        duration: Duration(milliseconds: 500),
        curve: Curves.easeOutQuint,
        width: _width / 2,
        height: _height,
        margin: EdgeInsets.only(top: top, bottom: 20, right: 20),
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(40),
            image: DecorationImage(
              fit: BoxFit.fill,
              image: NetworkImage(data['img']),
            ),
            boxShadow: [
              BoxShadow(
                  color: Colors.black87,
                  blurRadius: blur,
                  offset: Offset(offset, offset))
            ]),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Center(
                child: Text(data['quote'],
                    style: TextStyle(
                        fontSize: 20,
                        color: Colors.white,
                        fontFamily: "Typewriter")),
              ),
            ),
            SizedBox(height: 20),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(data['author'],
                  style: TextStyle(
                      fontSize: 20,
                      color: Colors.white,
                      fontFamily: "Typewriter")),
            ),
            SizedBox(height: 20),
            Row(mainAxisAlignment: MainAxisAlignment.end, children: [

              IconButton(
            icon: Icon(liked ? Icons.favorite : Icons.favorite_border,
                color: liked ? Colors.red : Colors.grey[50]),
            onPressed: (){
              
   
              
              _persistPreference();
              

            } ),
             
              
              IconButton(
                  icon: Icon(
                    Icons.share,
                    color: Colors.white,
                  ),
                  onPressed: () {
                    share();
                  })
            ])
          ],
        ));
  }
}


标签: firebaseflutterdartgoogle-cloud-firestore

解决方案


我会建议为喜欢制作另一个收藏。如果此应用程序要扩展,由于 Firestore 文档的大小限制为 1 MB,您将限制用户的报价数量。所以这将是一个建议的解决方案:

文档结构/likes/{likeId}

{
    'userId': string
    'quoteId': string
}

然后,如果您想加载喜欢的报价列表,您可以执行如下查询:

_db.collection(likes).where('userId', isEqualTo: currentUserId).get()

然后,您可以从那里根据上面的结构从包含在 likes 对象中的报价 ID 中加载报价。为了避免无缘无故加载大量文档,您还可以在查询中实现分页

因此,现在当有人想要报价时,您可以在 likes 集合中创建一个新的like 对象,其中包含报价 ID 和用户 ID,如上所示。

对于删除/取消喜欢,您需要找到一个与 userId 和 quoteId 都匹配的文档才能删除它

var snap = _db.collection(likes).where('userId', isEqualTo: currentUserId).where('quoteId', isEqualTo: quoteId).get();
if(snap.docs != null && snap.docs.isNotEmpty){
    //the record exists
    snap.docs.first.ref.delete() // this will delete the first record in the database that matches this like. Ideally there will only ever be one (you should check to make sure a document doesn't already exist before creating a new one)
}

在颤振方面,您可能会有一个 ListView 显示这些列表。我建议制作一个组件,该组件将使用未来的构建器从 id 加载报价。

如果您需要更多 Flutter 端代码示例,请告诉我。祝你好运!


推荐阅读