You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中借助Firestore追踪用户登录与登出事件?

问题描述

我正在开发一款Flutter应用,需要追踪用户的登录与登出事件,并将相关信息存储至Firestore集合中,具体需记录用户的姓名(FullName)、ID、园区位置(compound)以及登录/登出时间。已实现Tablescreen页面用于以表格形式展示这些数据,但不确定如何正确追踪登录/登出事件并同步更新Firestore。

当前实现存在的问题

  1. 登录页面未添加FullName、ID、compound的输入控件,导致相关控制器无数据,无法写入Firestore
  2. 登出方法_logout未绑定到界面按钮,无法触发登出逻辑
  3. 登录成功后连续跳转两个页面,逻辑冗余

咨询问题

  • 如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 13:48:10