Firestore 不会在数据库中创建文档(作为帐户创建的一部分),但只是有时

问题描述 投票:0回答:1

我有一个带有注册流程的 Flutter 应用程序,全部由 Firebase 管理。在此过程中,应在我的 Firestore 数据库中的

users
集合中为每个用户创建一个文档。我正在为我的应用程序运行一个试点计划。从该程序来看,大约 1/3 的用户在创建帐户期间不会在 Firestore 中创建文档。我无法使用生产代码复制此错误 - 每次我创建一个新帐户时,它都工作得很好。我不知道我是否注意到了错误的趋势,但我在西海岸,感觉很多出现此问题的人都在东海岸。就在今天,我有一位新用户在创建帐户时遇到了此错误,另一位则没有遇到此错误。以下是我的代码的相关部分:

// This class manages navigation after verified user logs in or a new user verifies their email
class AppNav extends StatefulWidget {
  const AppNav({super.key, required this.newUser});
  final bool newUser;

  @override
  State<AppNav> createState() => _AppNavState();
}

class _AppNavState extends State<AppNav> {
  late Future<List<dynamic>> _future;
  // An initState() is used so these methods are not called multiple times
  @override
  void initState() {
    super.initState();
    Future<List<dynamic>> initApp() async {
      await FirebaseAuth.instance.currentUser!.reload();
      await context.read<MyAppState>().readHintPages();
      if (widget.newUser) {
        await context.read<MyAppState>().createUserInDB();
      }
      await context.read<MyAppState>().readUser();

      context.read<MyAppState>().navigateTo(0);
      return Future.value([]);
    }

    _future = initApp();
  }

  @override
  Widget build(BuildContext context) {
    // The asynchronous methods are called here
    // Then, once the user and app are set up, the app navigates to the home page
    return FutureBuilder(
      future: _future,
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return ErrorPage(error: snapshot.error);
        } else if (snapshot.connectionState == ConnectionState.waiting) {
          return animatedLogo(context, true);
        } else {
          return MyHomePage(
            newUser: widget.newUser,
            title: '5 Minute संस्कृतम् ।',
          );
        }
      },
    );
  }
}

在另一个文件中,在

ChangeNotifier
类中:

  // This method creates a user document in the database with the user name
  // Errors from this method are handled in the parent widget
  Future createUserInDB() async {
    final userRef = FirebaseFirestore.instance.collection('users').doc(
          FirebaseAuth.instance.currentUser!.email!,
        );
    try {
      await userRef.set({
        'name': appUser.name,
        'quizStates': {},
        'lbPoints': 0,
        'seshHistory': {},
        'code': 'choose',
      });
      return Future.value();
    } catch (error) {
      return Future.error('(From createUserInDB) $error');
    }
  }

  // This method retrieves user data from the Firebase Firestore database
  // Errors from this method are handled in the parent widget
  Future readUser() async {
    final usersRef = FirebaseFirestore.instance.collection('users');

    try {
      QuerySnapshot<Map<String, dynamic>> usersSnapshot = await usersRef.get();
      lbUsers = [];

      // Each user document is iterated through and processed
      for (var doc in usersSnapshot.docs) {
        Map<String, dynamic> userMap = doc.data();

        // Check if this is the current user's document, and process if so
        if (doc.id == FirebaseAuth.instance.currentUser!.email!) {
          String code = userMap['code'];
          await readQuizzes(code);
          appUser = AppUser.fromMap(userMap);

          // Here, the current user's quiz data is retrieved
          for (Quiz quiz in quizzes) {
            Map<String, dynamic>? quizState = appUser.quizStates[quiz.name];
            if (quizState != null) {
              quiz.readFromState(quizState);
            }
          }
          updateMasteredQuizzes();
          // Otherwise, add user as a leaderboard user with limited points
        } else {
          lbUsers.add(LeaderboardUser.fromMap(userMap));
        }
      }
      return Future.value(appUser);
    } catch (error) {
      return Future.error('(From readUser) $error');
    }
  }

  // This method retrieves quiz data from the Firebase Firestore database
  // Errors from this method are handled in the parent widget
  Future readQuizzes(String code) async {
    try {
      // This snapshot is of the entire quiz collection
      var quizRef = FirebaseFirestore.instance.collection('quizzes');
      if (code == 'choose') {
        quizRef = quizRef.doc('Demo Quizzes').collection('all');
      } else {
        quizRef = quizRef.doc('Pilot Program').collection(code);
      }

      QuerySnapshot<Map<String, dynamic>> value = await quizRef.get();

      // Reset values before retrieving data from new account
      quizzes = [];
      masteredQuizzes = [];
      masteredQuizPoints = 0;
      currentQuiz = -1;
      currentQuizName = "";

      // Here, each quiz is added if it is to be shown
      for (DocumentSnapshot<Map<String, dynamic>> doc in value.docs) {
        Map<String, dynamic> quizMap = doc.data()!;
        Quiz quiz = Quiz.fromMap(quizMap);
        if (quiz.show && DateTime.now().isAfter(quiz.start)) {
          quizzes.add(quiz);
        }
      }

      await Future.delayed(const Duration(seconds: 4), () {});
      return Future.value(quizzes);
    } catch (error) {
      return Future.error('(From readQuizzes) $error');
    }
  }

正如所注意到的,该应用程序确实需要电子邮件验证,但所有遇到此错误的用户在验证其帐户后都会注意到它(他们从验证电子邮件页面转到主页,但主页是空白的,因为没有文档在 Firestore 中。

如果相关的话,这是我的 Firestore 规则:

rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow read access to any authenticated user
    match /{document=**} {
      allow read: if request.auth != null;
      
    // Allow full write access to the database only for the admin user
    allow write: if request.auth.token.email == '[email protected]';
    }

    // Allow write access to the "users" collection only if the document ID matches the user's email
    match /users/{userEmail} {
      allow write: if request.auth != null && request.auth.token.email == userEmail;
      
      
    }
  }
}

我只能通过删除 AuthFlow 类中的

readUser()
createUserInDB()
调用来完全重新创建此错误。由此,我猜测
initApp()
Future 方法中的 Firestore 方法都没有正确完成。我已经对代码进行了几次调整以修复此错误。例如,以前,我没有使用
initApp()
方法,而是在
Future.wait()
中拥有一个包含所有这些方法调用的 Future 列表。在
initApp()
中的所有调用之前用户重新加载也是新的,但这些都没有帮助。

database flutter firebase google-cloud-firestore
1个回答
0
投票

您可能需要考虑使用云功能在 Firestore 中创建该文档。这样的云函数可以触发 Firebase 身份验证中的用户创建,并且比在前端应用程序代码中执行此操作要可靠得多。

© www.soinside.com 2019 - 2024. All rights reserved.