如何在Flutter中借助Firestore追踪用户登录与登出事件?
问题描述
我正在开发一款Flutter应用,需要追踪用户的登录与登出事件,并将相关信息存储至Firestore集合中,具体需记录用户的姓名(FullName)、ID、园区位置(compound)以及登录/登出时间。已实现Tablescreen页面用于以表格形式展示这些数据,但不确定如何正确追踪登录/登出事件并同步更新Firestore。
当前实现存在的问题
- 登录页面未添加
FullName、ID、compound的输入控件,导致相关控制器无数据,无法写入Firestore - 登出方法
_logout未绑定到界面按钮,无法触发登出逻辑 - 登录成功后连续跳转两个页面,逻辑冗余
咨询问题
- 如何在Firestore中正确记录用户的登录与登出时间?
- 登出时如何更新Firestore文档中的登出时间及用户相关信息?
- 是否有更优的事件追踪方案?
解决方案
1. 正确记录登录/登出时间
步骤1:完善登录页面输入控件
在Signin页面的密码输入框后添加用户信息输入项,确保能获取到姓名、ID、园区位置:
// 密码输入框后新增三个输入控件 SizedBox(height: 8), TextFormField( controller: FullNamecontroler, textAlign: TextAlign.center, decoration: InputDecoration( labelText: 'Full Name', prefixIcon: Icon(Icons.person), contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10))), enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue, width: 1), borderRadius: BorderRadius.all(Radius.circular(10))), focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.red, width: 2), borderRadius: BorderRadius.all(Radius.circular(10))), ), ), SizedBox(height: 8), TextFormField( controller: IDcontroller, textAlign: TextAlign.center, decoration: InputDecoration( labelText: 'ID', prefixIcon: Icon(Icons.badge), contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10))), enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue, width: 1), borderRadius: BorderRadius.all(Radius.circular(10))), focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.red, width: 2), borderRadius: BorderRadius.all(Radius.circular(10))), ), ), SizedBox(height: 8), TextFormField( controller: compoundcontroller, textAlign: TextAlign.center, decoration: InputDecoration( labelText: 'Compound', prefixIcon: Icon(Icons.location_on), contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10))), enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue, width: 1), borderRadius: BorderRadius.all(Radius.circular(10))), focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.red, width: 2), borderRadius: BorderRadius.all(Radius.circular(10))), ), ),
步骤2:修正登录逻辑
移除重复跳转,确保登录成功后仅跳转到主页面,并正确写入Firestore:
// 登录按钮的onpressed回调修改 if (credential.user!.emailVerified) { final userDocRef = FirebaseFirestore.instance.collection('signing').doc(credential.user!.uid); await userDocRef.set({ 'FullName': FullNamecontroler.text.trim(), 'ID': IDcontroller.text.trim(), 'compound': compoundcontroller.text.trim(), 'login': FieldValue.serverTimestamp(), 'logout': null, }, SetOptions(merge: true)); // 仅跳转主页面,Tablescreen可从主页面入口进入 Navigator.pushReplacementNamed(context, Homescreen.screenroute); }
步骤3:完善登出逻辑
在主页面(Homescreen)添加登出按钮,触发登出并更新Firestore:
// 在Homescreen的build方法中添加登出按钮 ElevatedButton( onPressed: () async { final user = FirebaseAuth.instance.currentUser; if (user != null) { await FirebaseFirestore.instance.collection('signing').doc(user.uid).update({ 'logout': FieldValue.serverTimestamp(), }); await FirebaseAuth.instance.signOut(); Navigator.pushReplacementNamed(context, Signin.screenroute); } }, child: Text('Logout'), )
2. 登出时更新用户相关信息
- 建议用户信息(姓名、ID、园区位置)在修改时实时更新Firestore,避免集中到登出时处理
- 若必须在登出时更新,需从全局状态或页面控件中获取最新值,再执行更新:
// 登出时更新园区位置示例 await FirebaseFirestore.instance.collection('signing').doc(user.uid).update({ 'logout': FieldValue.serverTimestamp(), 'compound': currentCompoundValue, // 从状态管理或输入控件中获取最新值 });
3. 更优的事件追踪方案
方案一:封装独立Auth服务类
将登录/登出的Firestore操作与UI代码解耦,提高可维护性:
class AuthService { final FirebaseAuth _auth = FirebaseAuth.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance; Future<UserCredential?> signIn(String email, String password, String fullName, String id, String compound) async { try { final credential = await _auth.signInWithEmailAndPassword(email: email, password: password); if (credential.user!.emailVerified) { await _firestore.collection('signing').doc(credential.user!.uid).set({ 'FullName': fullName, 'ID': id, 'compound': compound, 'login': FieldValue.serverTimestamp(), 'logout': null, }, SetOptions(merge: true)); return credential; } else { await credential.user!.sendEmailVerification(); return null; } } on FirebaseAuthException catch (e) { print(e.code); return null; } } Future<void> signOut() async { final user = _auth.currentUser; if (user != null) { await _firestore.collection('signing').doc(user.uid).update({ 'logout': FieldValue.serverTimestamp(), }); await _auth.signOut(); } } }
方案二:监听Auth状态自动记录事件
通过FirebaseAuth状态监听,自动触发登录/登出的记录逻辑,无需手动绑定按钮:
@override void initState() { super.initState(); String? _lastLoggedInUserId; FirebaseAuth.instance.authStateChanges().listen((User? user) { if (user != null && user.emailVerified) { _lastLoggedInUserId = user.uid; // 自动记录登录时间 FirebaseFirestore.instance.collection('signing').doc(user.uid).update({ 'login': FieldValue.serverTimestamp(), 'logout': null, }); } else if (user == null && _lastLoggedInUserId != null) { // 自动记录登出时间 FirebaseFirestore.instance.collection('signing').doc(_lastLoggedInUserId!).update({ 'logout': FieldValue.serverTimestamp(), }); _lastLoggedInUserId = null; } }); }
方案三:分集合存储所有会话历史
如果需要保留用户每次登录/登出的完整记录,可创建独立的user_sessions集合,每次登录新增文档,登出时更新对应文档:
// 登录时新增会话文档 await _firestore.collection('user_sessions').add({ 'userId': credential.user!.uid, 'FullName': fullName, 'ID': id, 'compound': compound, 'loginTime': FieldValue.serverTimestamp(), 'logoutTime': null, }); // 登出时更新未结束的会话 await _firestore.collection('user_sessions') .where('userId', isEqualTo: user.uid) .where('logoutTime', isNull: true) .get() .then((snapshot) { if (snapshot.docs.isNotEmpty) { snapshot.docs.first.reference.update({ 'logoutTime': FieldValue.serverTimestamp(), }); } });
内容的提问来源于stack exchange,提问作者Hussien990
相关产品推荐
相关产品推荐

