Как сопоставить лайки / избранное конкретного пользователя? Аналогично функции «Нравится» в большинстве социальных приложений

#firebase #flutter #dart #google-cloud-firestore

#firebase #флаттер #dart #google-облако-firestore

Вопрос:

Привет, я самоучка, разработчик Flutter, кодирующий свое первое приложение. Я создаю базовое приложение для цитирования в Flutter, которое использует Firebase для аутентификации и FireStore в качестве базы данных. Вот схема FireStore NoSQL:

Существует коллекция историй, которая доступна только для чтения и содержит ссылки на изображения и текст, как показано ниже. Обратите внимание, что идентификатор документа автоматически генерируется в коллекции историй:

Коллекция историй

Затем есть коллекция пользователей, в которой хранятся пользовательские данные, такие как имя пользователя, адрес электронной почты, дата и время создания учетной записи и массив лайков [для потенциального хранения понравившихся историй]. В этой коллекции идентификатором документа является UID (уникальный идентификатор) аутентифицированного (вошедшего в систему) пользователя.

Коллекция пользователя

Итак, вот история пользователя, которую я преследую, и мой подход к ней:

Всякий раз, когда пользователь нажимает на значок избранного ниже, лайк должен быть сохранен в коллекции пользователей, где uid пользователя является идентификатором документа, и сохранить понравившуюся историю в массиве лайков.

Скриншот пользовательского интерфейса симулятора перед лайком

Затем используйте оператор if, в котором говорится, что если у аутентифицированного пользователя есть истории в массиве лайков, превратите очерченное белое сердце в красное, вот так:

Скриншот пользовательского интерфейса симулятора после лайка

Однако в моем коде есть ошибка, из-за которой сердца всех историй сразу становятся красными, когда пользователь нажимает на значок избранного. Может кто-нибудь, пожалуйста, помочь? Вот фрагмент кода:

 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();
                  })
            ])
          ],
        ));
  }
}


 

Ответ №1:

Я бы рекомендовал создать еще одну коллекцию лайков. Если бы это приложение масштабировалось, вы бы ограничивали своих пользователей количеством цитат, которые они хотели бы получить, из-за ограничения размера документа Firestore в 1 МБ. Итак, вот предлагаемое решение:

Структура документа для /likes/{likeId}

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

Затем, если вы хотите загрузить список понравившихся цитат, вы можете выполнить запрос типа:

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

Оттуда вы можете загрузить цитаты из идентификатора цитаты, включенного в объект «Нравится», в соответствии со структурой выше. Чтобы избежать загрузки тонны документов без причины, вы также можете реализовать разбивку на страницы в своем запросе.

Итак, теперь, когда кто-то хочет цитату, вы должны создать новый объект «Нравится» в коллекции «Нравится» с идентификатором цитаты и идентификатором пользователя, как показано выше.

Для удаления / непохожести вам нужно будет найти документ, который соответствует идентификатору пользователя и идентификатору цитаты, чтобы удалить его

 var snap = _db.collection(likes).where('userId', isEqualTo: currentUserId).where('quoteId', isEqualTo: quoteId).get();
if(snap.docs != null amp;amp; 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)
}
 

На стороне flutter у вас, вероятно, будет ListView, который показывает список из них. Я бы рекомендовал создать компонент, который загружал бы цитату из идентификатора с помощью future builder.

Если вам нужно больше примеров кода на стороне Flutter, дайте мне знать. Удачи!

Комментарии:

1. Привет, ты знаешь, как я могу составить список избранных пользователя? Я попытался использовать два streambuilder. первый поток предназначен для получения любимых цитат текущего пользователя, другой — для получения конкретных цитат с помощью QuoteID, но мне не удалось с этим справиться. Имеет ли это смысл?

Ответ №2:

В этой строке кода: liked = !liked; вы устанавливаете все свои экземпляры liked с их инвертированным значением, и это заставляет его применять like ко всем записям.

Попробуйте удалить это setState() в целом и применить перевернутый лайк вне этого, вот так:

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

ПРИМЕЧАНИЕ: в идеале значение liked должно быть получено из коллекции пользователей в Firestore, но я понимаю, что это, вероятно, следующий шаг в вашем проекте, поэтому этот обходной путь должен сработать на этом этапе. Для получения его из Firestore вам iconbutton нужно будет заполнить что-то вроде этого (не тестировалось, поэтому может потребоваться адаптация):

 //user is a preloaded variable with the current user data
IconButton(icon: Icon(user.data['likes'].contains(data['uid']) ? Icons.favorite ...))
 

и используйте эту функцию в той же _persistPreference() функции, обновляя ее в Firestore.

Комментарии:

1. Большое спасибо за ваше предложение @Rafael. Я попытался удалить setState в функции persistPreference void, но это, похоже, не сработало.