Flutter中Firestore记录用户登录登出信息及表格展示技术咨询
Flutter 用户登录追踪实现与优化建议
需求背景
开发Flutter应用,需追踪用户登录/登出时间、全名、ID、园区位置,并在独立页面的表格中展示这些信息。
当前实现代码
记录登录时间
登录成功时写入用户信息与登录时间:
final userDocRef = FirebaseFirestore.instance.collection('signing').doc(credential.user!.uid); // Log login time await userDocRef.set({ 'FullName': FullNamecontroler.text, 'ID': IDcontroller.text, 'compound': compoundcontroller.text, 'login': FieldValue.serverTimestamp(), 'logout': null, }, SetOptions(merge: true));
记录登出时间
登出时更新登出时间:
void _logout() async { final user = FirebaseAuth.instance.currentUser; if (user != null) { final userDocRef = FirebaseFirestore.instance.collection('signing').doc(user.uid); // Log logout time await userDocRef.update({ 'logout': FieldValue.serverTimestamp(), }); await FirebaseAuth.instance.signOut(); Navigator.pushNamedAndRemoveUntil(context, Signin.screenroute, (route) => false); } }
表格展示数据
表格页面通过StreamBuilder实时获取并展示数据:
class Tablescreen extends StatefulWidget { static const String screenroute = 'Tablescreen'; const Tablescreen({Key? key}) : super(key: key); @override State<Tablescreen> createState() => _TablescreenState(); } class _TablescreenState extends State<Tablescreen> with SingleTickerProviderStateMixin { late TabController _controller; @override void initState() { super.initState(); _controller = TabController(length: 3, vsync: this, initialIndex: 0); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( bottom: TabBar( controller: _controller, tabs: [ Tab(text: "Table"), Tab(text: "Select Table"), Tab(text: "Sort Table"), ], ), title: Center( child: Text( " Daily Reports", style: TextStyle(fontWeight: FontWeight.w800), ), ), ), body: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Container( child: StreamBuilder<QuerySnapshot>( stream: FirebaseFirestore.instance.collection('signing').snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return CircularProgressIndicator(); } else if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { return Center(child: Text('No data available')); } else { final data = snapshot.data!.docs; return DataTable( columns: <DataColumn>[ DataColumn(label: Text("FullName")), DataColumn(label: Text("login")), DataColumn(label: Text("ID")), DataColumn(label: Text("compound")), DataColumn(label: Text("logout")), ], rows: data.map<DataRow>((DocumentSnapshot document) { final Map<String, dynamic> userData = document.data() as Map<String, dynamic>; // Format timestamps String formatTimestamp(Timestamp? timestamp) { if (timestamp == null) return 'N/A'; return timestamp.toDate().toString(); } return DataRow( cells: <DataCell>[ DataCell(Text(userData['FullName'] ?? '')), DataCell(Text(formatTimestamp(userData['login'] as Timestamp?))), DataCell(Text(userData['ID'] ?? '')), DataCell(Text(userData['compound'] ?? '')), DataCell(Text(formatTimestamp(userData['logout'] as Timestamp?))), ], ); }).toList(), ); } }, ), ), ), ); } }
疑问解答与优化建议
1. 如何正确记录登录/登出时间及用户附加信息?
当前实现逻辑可行,可通过以下优化确保准确性与规范性:
- 字段名统一:遵循Firestore惯例使用小驼峰命名(如
fullName代替FullName),保持命名一致性。 - 非空处理:对控制器输入做校验,避免空字符串存入,例如
FullNamecontroler.text.trim().isEmpty ? 'Unknown' : FullNamecontroler.text.trim()。 - 服务器时间优先:
FieldValue.serverTimestamp()是正确选择,可避免客户端时间不一致问题。 - 合并策略:
SetOptions(merge: true)确保用户多次登录时仅更新login时间,保留已有用户信息;若用户基础信息固定,可判断文档是否存在,仅首次登录时写入基础信息。
2. 如何在用户登出时正确更新登出时间?
当前登出逻辑顺序正确(先更新Firestore再执行signOut),需补充异常处理避免流程阻塞:
void _logout() async { final user = FirebaseAuth.instance.currentUser; if (user != null) { final userDocRef = FirebaseFirestore.instance.collection('signing').doc(user.uid); try { // 先更新登出时间 await userDocRef.update({ 'logout': FieldValue.serverTimestamp(), }); // 再执行登出跳转 await FirebaseAuth.instance.signOut(); Navigator.pushNamedAndRemoveUntil(context, Signin.screenroute, (route) => false); } catch (e) { // 处理更新失败的情况,如弹窗提示用户 ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('登出记录保存失败: ${e.toString()}')), ); } } }
Firestore默认开启离线持久化,即使离线触发登出,更新操作会在联网后自动同步,无需额外处理。
3. 更优的用户追踪方案
(1)存储历史登录会话
当前仅保留最后一次登录记录,若需追踪所有历史行为,建议创建signing_sessions集合,每次登录新增一个文档:
// 登录时写入新会话 await FirebaseFirestore.instance.collection('signing_sessions').add({ 'uid': credential.user!.uid, 'fullName': FullNamecontroler.text.trim(), 'id': IDcontroller.text.trim(), 'compound': compoundcontroller.text.trim(), 'loginTime': FieldValue.serverTimestamp(), 'logoutTime': null, });
登出时通过会话ID或uid+登录时间匹配文档更新logoutTime,完整保留用户所有登录历史,便于后续统计分析。
(2)封装数据库操作
将用户追踪逻辑抽离到单独服务类,避免业务代码与数据库操作耦合:
class UserTrackingService { final FirebaseFirestore _firestore = FirebaseFirestore.instance; Future<void> logLogin(User user, String fullName, String id, String compound) async { await _firestore.collection('signing').doc(user.uid).set({ 'fullName': fullName, 'id': id, 'compound': compound, 'loginTime': FieldValue.serverTimestamp(), 'logoutTime': null, }, SetOptions(merge: true)); } Future<void> logLogout(User user) async { await _firestore.collection('signing').doc(user.uid).update({ 'logoutTime': FieldValue.serverTimestamp(), }); } }
(3)优化表格展示
- 时间格式化:使用
intl包的DateFormat将时间戳转为友好格式,例如DateFormat('yyyy-MM-dd HH:mm:ss').format(timestamp.toDate())。 - 实现筛选与排序:利用TabBar的另外两个标签,添加按园区筛选、按登录时间排序功能,通过Firestore的
where()和orderBy()实现。 - 权限控制:配置Firestore安全规则,仅允许管理员角色访问登录记录集合,避免普通用户篡改或查看他人数据。
内容的提问来源于stack exchange,提问作者Hussien990
相关产品推荐
相关产品推荐

