Firebase 如何映射用户特定的喜好/收藏夹?类似于大多数社交应用上的“喜欢”功能
你好,我是一个自学成才的颤振开发人员,编写了我的第一个应用程序。我正在Flitter中构建一个基本的quotes应用程序,它使用Firebase for auth和FireStore作为数据库。以下是FireStore noSQL模式: 有一个故事集,它是只读数据,提供如下所示的图像链接和文本。注意:文档ID在Stories集合中自动生成: 然后是一个用户集合,它存储用户数据,如用户名、电子邮件、帐户创建日期时间和likes数组[可能存储喜欢的故事]。在此集合中,文档的ID是经过身份验证(登录)的用户的UID(唯一ID) 下面是我所追求的用户故事和我的方法: 每当用户点击下面的收藏夹图标时,like必须保存到users集合中,其中user's uid是文档ID,并将like故事存储在likes数组中 然后利用一条if语句,说明如果经过身份验证的用户在likes数组中有故事,请将勾勒出的白色心脏变成如下所示的红色心脏:Firebase 如何映射用户特定的喜好/收藏夹?类似于大多数社交应用上的“喜欢”功能,firebase,flutter,dart,google-cloud-firestore,Firebase,Flutter,Dart,Google Cloud Firestore,你好,我是一个自学成才的颤振开发人员,编写了我的第一个应用程序。我正在Flitter中构建一个基本的quotes应用程序,它使用Firebase for auth和FireStore作为数据库。以下是FireStore noSQL模式: 有一个故事集,它是只读数据,提供如下所示的图像链接和文本。注意:文档ID在Stories集合中自动生成: 然后是一个用户集合,它存储用户数据,如用户名、电子邮件、帐户创建日期时间和likes数组[可能存储喜欢的故事]。在此集合中,文档的ID是经过身份验证(登录
void _persistPreference() async {
var preferences = await SharedPreferences.getInstance();
preferences.setBool(likedKey, !liked);
}
然而,我的代码中有一个bug,每当用户点击最喜欢的图标时,它会立刻将所有故事的心变成红色。有人能帮忙吗?下面是一段代码:
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();
})
])
],
));
}
}
类FirestoreSlideshowState扩展状态{
静态常量likedKey='liked_key';
布尔喜欢;
final PageController ctrl=PageController(viewportFraction:0.8);
最终Firestore db=Firestore.instance;
溪流滑坡;
字符串activeTag='favs';
//跟踪当前页面以避免不必要的渲染
int currentPage=0;
@凌驾
void initState(){
super.initState();
_恢复PersistedReference();
_queryDb();
//设置页面更改时的状态
ctrl.addListener((){
int next=ctrl.page.round();
如果(当前页!=下一页){
设置状态(){
当前页面=下一页;
});
}
});
}
void\u restorePersistedReference()异步{
var preferences=await SharedPreferences.getInstance();
var liked=preferences.getBool(likedKey)?false;
设置状态(){
喜欢;喜欢;
});
}
void\u persistPreference()异步{
设置状态(){
喜欢=!喜欢;
});
var preferences=await SharedPreferences.getInstance();
setBool(likedKey,liked);
}
@凌驾
小部件构建(构建上下文){
返回流生成器(
流:幻灯片,
初始数据:[],
生成器:(上下文,异步快照快照){
List slideList=snap.data.toList();
返回PageView.builder(
控制器:ctrl,
itemCount:slideList.length,
itemBuilder:(上下文,int currentIdx){
如果(slideList.length>=currentIdx){
//活动页
bool active=currentIdx==currentPage;
返回_buildStoryPage(slideList[currentIdx],活动);
}
});
});
}
流_queryDb({String tag='favs'}){
//询问
Query Query=db.collection('Stories')。其中('tags',arrayContains:tag);
//将文档映射到数据有效负载
幻灯片=
query.snapshots().map((list)=>list.documents.map((doc)=>doc.data));
//更新活动标记
设置状态(){
activeTag=tag;
});
}
_buildStoryPage(地图数据,布尔激活){
final _width=MediaQuery.of(context).size.width;
final _height=MediaQuery.of(context).size.height;
//动画特性
最终双模糊=激活?20:0;
最终双偏移=激活?20:0;
最终双层顶部=有效?75:150;
返回动画容器(
持续时间:持续时间(毫秒:500),
曲线:Curves.easeOutQuint,
宽度:_宽度/2,
高度:_高度,
边距:仅限边集(顶部:顶部,底部:20,右侧:20),
装饰:盒子装饰(
边界半径:边界半径。圆形(40),
图像:装饰图像(
fit:BoxFit.fill,
图像:网络图像(数据['img']),
),
boxShadow:[
箱形阴影(
颜色:颜色。黑色87,
模糊半径:模糊,
偏移:偏移(偏移,偏移))
]),
子:列(
mainAxisAlignment:mainAxisAlignment.center,
crossAxisAlignment:crossAxisAlignment.end,
儿童:[
填充物(
填充:常数边集全部(8.0),
儿童:中心(
子项:文本(数据['quote'],
样式:TextStyle(
尺寸:20,
颜色:颜色,白色,
fontFamily(“打字机”),
),
),
尺寸箱(高度:20),
填充物(
填充:常数边集全部(8.0),
子:文本(数据['author'],
样式:TextStyle(
尺寸:20,
颜色:颜色,白色,
fontFamily(“打字机”),
),
尺寸箱(高度:20),
行(mainAxisAlignment:mainAxisAlignment.end,子项:[
图标按钮(
图标:图标(喜欢?图标。收藏:图标。收藏\u边框,
颜色:喜欢?颜色。红色:颜色。灰色[50]),
已按下:(){
_持久偏好();
} ),
图标按钮(
图标:图标(
Icons.share,
颜色:颜色,白色,
),
已按下:(){
分享();
})
])
],
));
}
}
在这行代码中:=
//user is a preloaded variable with the current user data
IconButton(icon: Icon(user.data['likes'].contains(data['uid']) ? Icons.favorite ...))
{
'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 && 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)
}