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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 05:34:52