Flutter中如何对比Firebase Realtime DB键值获取当前登录用户数据
实现方案
你现在的写法有两个核心问题:一是直接拉取了insertdata节点下的全量用户数据,既浪费流量、查询慢,还存在数据安全风险;二是获取用户邮箱的方法是异步执行的,initState里调用不会等待赋值完成就触发build,很容易出现邮箱为空、匹配不到数据的情况。
根据你是否愿意调整现有数据库结构,有两种可行方案:
方案1:用UID作为用户数据的存储键(长期使用强烈推荐)
这是Firebase开发的标准实践,完全不需要做字段匹配,查询效率最高。
你之前写入用户数据的时候应该是用了push()生成随机子节点ID,改成直接用Firebase Auth返回的用户uid作为子节点键即可,写入示例:
final currentUser = FirebaseAuth.instance.currentUser; if (currentUser != null) { // 直接用uid当key存用户数据 FirebaseDatabase.instance.ref("insertdata").child(currentUser.uid).set({ "Email": currentUser.email, "First Name": "输入的名", "Last Name": "输入的姓", // 其他用户字段 }); }
这种结构下,要获取当前登录用户的快照根本不需要匹配任何字段,直接定位路径查询即可:
// 先判断登录状态 final currentUser = FirebaseAuth.instance.currentUser; if (currentUser == null) { // 处理未登录逻辑,比如跳转登录页 return; } // 直接拿当前uid对应节点的快照 final userSnapshot = await FirebaseDatabase.instance .ref("insertdata") .child(currentUser.uid) .get(); if (userSnapshot.exists) { final userData = userSnapshot.value as Map<dynamic, dynamic>; // 直接使用userData里的字段即可 }
如果需要实时监听用户数据变化,把.get()替换成.onValue流,配合StreamBuilder使用就行。
方案2:兼容现有数据库结构(无需修改已有数据)
如果你已经存了大量历史数据,不想调整结构,可以用Realtime Database的查询语法直接在服务端过滤出邮箱匹配的节点,不需要拉全量数据到本地遍历。
注意:用这个方案需要给Email字段加数据库索引,否则数据量上来后查询性能会很差,索引配置方法后面会说。
直接替换你原来Scaffold里的FutureBuilder部分即可:
body: FutureBuilder<DatabaseEvent>( future: () async { final currentUser = FirebaseAuth.instance.currentUser; if (currentUser == null) throw "用户未登录"; // 服务端直接过滤Email等于当前用户邮箱的节点,只取1条结果 final userQuery = dbRef.orderByChild("Email").equalTo(currentUser.email).limitToFirst(1); return userQuery.once(); }(), builder: (context, snapshot) { if (snapshot.hasError) { return Center(child: Text("数据获取失败:${snapshot.error}")); } if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } final rawData = snapshot.data!.snapshot.value; if (rawData == null) { return const Center(child: Text("暂无对应用户数据")); } // 查询返回的是匹配到的节点集合,取第一个就是当前用户的数据 final matchedMap = rawData as Map<dynamic, dynamic>; final userData = matchedMap.values.first as Map<dynamic, dynamic>; return Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("邮箱:${userData["Email"]}"), Text("名:${userData["First Name"]}"), Text("姓:${userData["Last Name"]}"), ], ), ); }, )
如果用这个方案,记得去Firebase控制台的Realtime Database规则页面,给insertdata节点加Email字段的索引,规则参考:
{ "rules": { "insertdata": { ".indexOn": ["Email"], ".read": "auth != null", ".write": "auth != null" } } }
原有代码的其他修正点
- 你现在的字段映射写反了:把Email显示成Name,First Name显示成Age,Last Name显示成Type,实际使用的时候记得对应上。
- 异步获取的变量不要在initState里直接调用不等待,很容易出现空指针,把获取登录用户的逻辑直接放到异步查询闭包里,能从根源上避免时序问题。
- 任何时候都不要拉全量用户数据到本地处理,不管是从流量消耗、查询速度还是数据安全角度,这种写法都不合理,能在服务端完成的过滤/定位就不要放到端上做。
[数据库结构参考截图]
附你提供的原有实现代码:
import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/ui/firebase_animated_list.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:helloworld/tictactoe.dart'; import 'package:helloworld/numbergame.dart'; import 'package:helloworld/profile.dart'; class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( // Remove the debug banner debugShowCheckedModeBanner: false, title: 'Search', home: Search(), ); } } class Search extends StatefulWidget { const Search({Key? key}) : super(key: key); static const String id = 'search'; @override _SearchState createState() => _SearchState(); } class _SearchState extends State<Search> with AutomaticKeepAliveClientMixin { final dbRef = FirebaseDatabase.instance.ref().child("insertdata"); List<Map<dynamic, dynamic>> lists = []; final _auth = FirebaseAuth.instance; late String? userEmail; void getCurrentUserEmail() async { final user = await _auth.currentUser; userEmail = user!.email; } void initState() { super.initState(); getCurrentUserEmail(); } @override Widget build(BuildContext context) { super.build(context); //btmnavbar return Scaffold( resizeToAvoidBottomInset: false, body: FutureBuilder<DatabaseEvent>( future: dbRef.once(), builder: (context, AsyncSnapshot<DatabaseEvent> snapshot) { if (snapshot.hasData) { lists.clear(); Map<dynamic, dynamic> values = snapshot.data!.snapshot.value as Map<dynamic, dynamic>; values.forEach((key, values) { lists.add(values); }); return new ListView.builder( shrinkWrap: true, itemCount: lists.length, itemBuilder: (BuildContext context, int index) { return Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text("Name: " + lists[index]["Email"]), Text("Age: " + lists[index]["First Name"]), Text("Type: " + lists[index]["Last Name"]), ], ), ); }); } return CircularProgressIndicator(); }), ); } @override // TODO: implement wantKeepAlive bool get wantKeepAlive => true; }
内容的提问来源于stack exchange,提问作者User101
相关产品推荐
相关产品推荐

