#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, но это, похоже, не сработало.