如何直接从 Flutter 代码创建新的 Firestore 用户集合

How to create a new Firestore user collection directly from Flutter code

我已经能够让 Firebase 身份验证用于 Google 登录、匿名登录以及从电子邮件和密码登录,包括在电子邮件和密码登录期间发送验证电子邮件感谢帮助堆栈溢出。一切都按预期工作。现在我的下一步是尝试使用 Firebase 身份验证创建的 uid 在 Firestore 中创建用户集合。我确信我的代码编写正确,因为我已经使用(不安全的)安全规则对其进行了测试,并且该过程完全按预期工作。 我已经多次查看 Firebase 文档,但我无法弄清楚我的安全规则代码有什么问题。如何修复我的安全规则以允许新用户创建将添加到 Firestore 中的用户集合的屏幕名称?先谢谢您的帮助。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{uid}/jobs/{document=**} {
       allow read, write: if request.auth.uid == uid;
  }

  match /users/{uid}/{document=**} {
       allow read, write: if request.auth.uid == uid;
  }

  }
  
  }


   class HomePage extends StatefulWidget {
      const HomePage({
        Key? key,
        
      }) : super(key: key);
    
      
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      @override
      void initState() {
        super.initState();
        createUserInFirestore();
      }
    
      Future<void> createUserInFirestore() async {
        final GoogleSignIn googleSignIn = GoogleSignIn();
        final GoogleSignInAccount? user = googleSignIn.currentUser;
        final usersRef = FirebaseFirestore.instance.collection('users');
        final DocumentSnapshot doc = await usersRef.doc(user?.id).get();
        if (!doc.exists) {
          final userName = await Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => const CreateAccountPage(),
            ),
          );
          usersRef.doc(user?.id).set({
            'id': user?.id,
            'userName': userName,
            'photoUrl': user?.photoUrl,
            'email': user?.email,
            'displayName': user?.displayName,
            'bio': '',
            'timestamp': documentIdFromCurrentDate(),
          });
        doc = await usersRef.doc(user?.id).get();
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return AdaptiveLayoutScaffold(
          drawer: const SideSheet(
            userImage: FakeUserAvatars.stacy,
            userName: 'Stacy James',
          ),
          landscapeBodyWidget: Container(),
          portraitBodyWidget: Container(),
        );
      }
    }

class CreateAccountPage extends StatefulWidget {
  const CreateAccountPage({
    Key? key,
  }) : super(key: key);

  @override
  _CreateAccountPageState createState() => _CreateAccountPageState();
}

class _CreateAccountPageState extends State<CreateAccountPage> {
  final _formKey = GlobalKey<FormState>();
  late String userName;

  void submit() {
    _formKey.currentState?.save();
    Navigator.pop(context, userName);
  }

  @override
  Widget build(BuildContext context) {
    return AdaptiveLayoutScaffold(
      appBar: const Header(
        automaticallyImplyLeading: false,
        pageName: 'User Name',
      ),
      landscapeBodyWidget: Container(),
      portraitBodyWidget: ListView(
        children: [
          Column(
            children: [
              const Padding(
                padding: EdgeInsets.only(top: 16.0),
                child: Center(
                  child: Text('Create a User Name'),
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Form(
                  key: _formKey,
                  child: TextFormField(
                    autovalidateMode: AutovalidateMode.always,
                    decoration: InputDecoration(
                      hintText: 'Must be between 3 and 20 characters',
                      labelText: 'User Name',
                      prefixIcon: Icon(
                        Icons.person,
                        color: Theme.of(context).iconTheme.color,
                      ),
                    ),
                    keyboardType: TextInputType.text,
                    onSaved: (val) => userName = val as String,
                  ),
                ),
              ),
              PlatformElevatedButton(
                onPressed: submit,
                buttonText: 'Create User Name',
              ),
            ],
          ),
        ],
      ),
    );
  }
}

阅读建议内容和其他一些内容后,我使用 Firestore Rules Playground 修复了我的代码,然后更新了我的 Auth class 以包含一个名为 createUserInFirestore() 的新方法来处理Firebase 身份验证创建用户后,Firestore 中使用 uid 的新用户。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
  
    match /users/{uid}/{document=**} {
       allow read, create, update, delete: if request.auth.uid == uid;  
    }
  } 
}

abstract class AuthBase {
  User? get currentUser;

  Stream<User?> authStateChanges();
  Future<User?> signInWithGoogle();
  Future<User?> createUserWithEmailAndPassword(
    String email,
    String password,
  );
  Future<void> checkEmailVerified(BuildContext context, Timer timer);
  Future<User?> signInWithEmailAndPassword(String email, String password);
  Future<User?> signInAnonymously();
  Future<void> resetPassword(BuildContext context, String email);
  Future<void> confirmSignOut(BuildContext context);
  Future<void> signOut();
}

class Auth implements AuthBase {
  final _firebaseAuth = FirebaseAuth.instance;

  @override
  User? get currentUser => _firebaseAuth.currentUser;

  @override
  Stream<User?> authStateChanges() => _firebaseAuth.authStateChanges();

  void _createNewUserInFirestore() {
    final User? user = currentUser;
    final CollectionReference<Map<String, dynamic>> usersRef =
        FirebaseFirestore.instance.collection('users');
    usersRef.doc(user?.uid).set({
      'id': user?.uid,
      'screenName': '',
      'displayName': user?.displayName,
      'photoUrl': user?.photoURL,
      'bio': '',
      'darkMode': false,
      'timestamp': documentIdFromCurrentDate(),
    });
  }

  @override
  Future<User?> signInWithGoogle() async {
    final GoogleSignIn googleSignIn = GoogleSignIn();
    final GoogleSignInAccount? googleUser = await googleSignIn.signIn();
    if (googleUser != null) {
      final googleAuth = await googleUser.authentication;
      if (googleAuth.idToken != null) {
        final UserCredential userCredential =
            await _firebaseAuth.signInWithCredential(
          GoogleAuthProvider.credential(
            idToken: googleAuth.idToken,
            accessToken: googleAuth.accessToken,
          ),
        );
        _createNewUserInFirestore();
        return userCredential.user;
      } else {
        throw FirebaseAuthException(
          code: FirebaseExceptionString.missingGoogleIDTokenCode,
          message: FirebaseExceptionString.missingGoogleIDTokenMessage,
        );
      }
    } else {
      throw FirebaseAuthException(
        code: FirebaseExceptionString.abortedByUserCode,
        message: FirebaseExceptionString.canceledByUserMessage,
      );
    }
  }

  @override
  Future<User?> createUserWithEmailAndPassword(
    String email,
    String password,
  ) async {
    final UserCredential userCredential =
        await _firebaseAuth.createUserWithEmailAndPassword(
      email: email,
      password: password,
    );
    _createNewUserInFirestore();
    return userCredential.user;
  }

  @override
  Future<void> checkEmailVerified(BuildContext context, Timer timer) async {
    final User? user = currentUser;
    await user?.reload();
    final User? signedInUser = user;
    if (signedInUser != null && signedInUser.emailVerified) {
      timer.cancel();
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(
          builder: (context) => const HomePage(),
        ),
      );
    }
  }

  @override
  Future<User?> signInWithEmailAndPassword(
    String email,
    String password,
  ) async {
    final UserCredential userCredential =
        await _firebaseAuth.signInWithCredential(
      EmailAuthProvider.credential(
        email: email,
        password: password,
      ),
    );
    return userCredential.user;
  }

  @override
  Future<void> resetPassword(
    BuildContext context,
    String email,
  ) async {
    try {
      await _firebaseAuth.sendPasswordResetEmail(email: email);
      Navigator.of(context).pop();
    } catch (e) {
      print(
        e.toString(),
      );
    }
  }

  @override
  Future<User?> signInAnonymously() async {
    final UserCredential userCredential =
        await _firebaseAuth.signInAnonymously();
    return userCredential.user;
  }

  Future<void> _signOut(BuildContext context) async {
    try {
      final AuthBase auth = Provider.of<AuthBase>(
        context,
        listen: false,
      );
      await auth.signOut();
      Navigator.pushAndRemoveUntil<dynamic>(
        context,
        MaterialPageRoute<dynamic>(
          builder: (BuildContext context) => const LandingPage(),
        ),
        (route) => false,
      );
    } catch (e) {
      print(
        e.toString(),
      );
    }
  }

  @override
  Future<void> confirmSignOut(BuildContext context) async {
    final bool? didRequestSignOut = await showAlertDialog(
      context,
      cancelActionText: DialogString.cancel,
      content: DialogString.signOutAccount,
      defaultActionText: DialogString.signOut,
      title: DialogString.signOut,
    );
    if (didRequestSignOut == true) {
      _signOut(context);
    }
  }

  @override
  Future<void> signOut() async {
    final GoogleSignIn googleSignIn = GoogleSignIn();
    await googleSignIn.signOut();
    await _firebaseAuth.signOut();
  }
}