我有一个带有注册流程的 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()
中的所有调用之前用户重新加载也是新的,但这些都没有帮助。
您可能需要考虑使用云功能在 Firestore 中创建该文档。这样的云函数可以触发 Firebase 身份验证中的用户创建,并且比在前端应用程序代码中执行此操作要可靠得多。