Search code examples
flutterdartgoogle-cloud-firestorefirebase-authentication

How to check if a user's email exists during textformfield asynchronous validation in Flutter with Firebase


I'm developing a flutter app with firebase, and I have a user registration form with email validation page. I want to check if the entered email already exists in the firebase database during the validation process. However, I've encountered a challenge – I can't use async within the validation function:

The argument type 'Future<String?> Function(String?)' can't be assigned to the parameter type 'String? Function(String?)?'

How can I achieve this email existence check without compromising form validation?

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../../components/validation.dart';

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

  @override
  State<SignUpView> createState() => _SignUpViewState();
}

class _SignUpViewState extends State<SignUpView> {
  final formKey = GlobalKey<FormState>();
  bool obscureText = true;
  final fullNameController = TextEditingController();
  final passwordController = TextEditingController();
  final emailController = TextEditingController();
  String? emailValidationError;

  void togglePasswordVisibility() {
    setState(() {
      obscureText = !obscureText;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Center(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(16.0),
            child: buildSignUpForm(context),
          ),
        ),
      ),
    );
  }

  Widget buildSignUpForm(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        Image.asset(
          'your_image_path',
          width: 100,
          height: 100,
        ),
        SizedBox(height: MediaQuery.of(context).size.height * 0.05),
        Text(
          'Create your account',
          style: Theme.of(context).textTheme.titleLarge?.copyWith(
            fontWeight: FontWeight.w600,
            color: Colors.black,
          ),
        ),
        const SizedBox(height: 12),
        const Text(
          'Join us today! '
              'Create your account by filling out the required information and start exploring our services. '
              'Sign up now and be a part of our community.',
          textAlign: TextAlign.center,
        ),
        const SizedBox(height: 24),
        Form(
          key: formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              buildTextFormField(
                label: 'Full Name',
                controller: fullNameController,
                validator: Validators.fullNameValidation,
              ),
              const SizedBox(height: 12),
              buildTextFormField(
                label: 'Email',
                controller: emailController,
                keyboardType: TextInputType.emailAddress,
                validator: (email) {
                  String? emailError = Validators.emailValidator(email);
                  if (emailError != null) {
                    setState(() {
                      emailValidationError = emailError;
                    });
                    return emailError;
                  } else {
                    emailValidationError = null; // Clear the error message
                    return null;
                  }
                },
              ),
              const SizedBox(height: 12),
              buildPasswordField(),
            ],
          ),
        ),
        const SizedBox(height: 24),
        ElevatedButton(
          onPressed: () async {
            if (formKey.currentState!.validate()) {
              try {
                await signUpWithEmailAndPassword();
              } catch (error) {
                if (kDebugMode) {
                  print('Error during registration: $error');
                }
              }
            }
          },
          child: const Text('Sign Up'),
        ),
      ],
    );
  }

  Widget buildTextFormField({
    required String label,
    required TextEditingController controller,
    TextInputType? keyboardType,
    String? hintText,
    String? Function(String?)? validator,
  }) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          label,
          style: Theme.of(context).textTheme.titleMedium,
        ),
        const SizedBox(height: 12),
        TextFormField(
          controller: controller,
          keyboardType: keyboardType,
          textInputAction: TextInputAction.next,
          decoration: InputDecoration(
            hintText: hintText ?? 'Enter your $label',
          ),
          autovalidateMode: AutovalidateMode.onUserInteraction,
          validator: validator,
        ),
      ],
    );
  }

  Widget buildPasswordField() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Password',
          style: Theme.of(context).textTheme.titleMedium,
        ),
        const SizedBox(height: 12),
        TextFormField(
          controller: passwordController,
          obscureText: obscureText,
          decoration: InputDecoration(
            hintText: 'Enter your password',
            suffixIcon: IconButton(
              icon: Icon(obscureText ? Icons.visibility_off : Icons.visibility),
              onPressed: togglePasswordVisibility,
            ),
          ),
          autovalidateMode: AutovalidateMode.onUserInteraction,
          validator: Validators.passwordValidator,
        ),
      ],
    );
  }

  Future<void> signUpWithEmailAndPassword() async {
    await FirebaseAuth.instance.createUserWithEmailAndPassword(
      email: emailController.text,
      password: passwordController.text,
    );

    User? user = FirebaseAuth.instance.currentUser;
    if (user != null) {
      await user.sendEmailVerification();
      await FirebaseFirestore.instance.collection('userdata').doc(user.uid).set(
        {
          'fullname': fullNameController.text,
        },
      );
    }
  }

  Future<bool> isEmailAlreadyInUse(String email) async {
    try {
      final user = await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
      if (user.isNotEmpty) {
        return true;
      }
      return false;
    } catch (error) {
      if (kDebugMode) {
        print('Error checking if email is in use: $error');
      }
      return false;
    }
  }
}

My goal is to perform this email existence check during form validation without compromising the validation process. What is the best way to achieve this?


Solution

  • I have omitted the 'Future' function from my codebase. The reason for this decision was that the function was initially instantiated after the Firebase signup validation process, which resulted in inaccurate data being returned. Consequently, I have revised my approach to email validation and now perform it within the context of the Firebase signup process.

    Future<void> signUpWithEmailAndPassword() async {
        try {
          // Perform user registration with Firebase Authentication
          await FirebaseAuth.instance.createUserWithEmailAndPassword(
            email: emailController.text,
            password: passwordController.text,
          );
    
          User? user = FirebaseAuth.instance.currentUser;
          if (user != null) {
            // Send email verification and store user data in Firebase Firestore
            await user.sendEmailVerification();
            await FirebaseFirestore.instance.collection('userdata').doc(user.uid).set(
              {
                'fullname': fullNameController.text,
              },
            );
          }
        } catch (error) {
            if (error is FirebaseAuthException) {
              // Handle Firebase Authentication errors
              if (error.code == 'email-already-in-use') {
                // Handle the case where the email is already in use
                // You can display a message to the user or take appropriate action.
                emailError = 'Email address is not available';
              } else {
                // Handle other Firebase Authentication errors
                // You can log or display the error message to aid in debugging.
                if (kDebugMode) {
                  print('Firebase Authentication Error: ${error.code} - ${error.message}');
                }
              }
            } else {
              if (kDebugMode) {
                print('An error occurred: $error');
              }
            }
        }
      }