skip to Main Content

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?

2

Answers


  1. Chosen as BEST ANSWER

    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');
              }
            }
        }
      }
    

  2. You can’t directly use async operations within the validator function because it expects a synchronous return. Instead, you can perform the check asynchronously and update the validation state upon completion. Here’s a streamlined approach:

    1. Add a state variable to store the email validation error:
    String? emailError;
    
    1. Use an asynchronous function to check if the email exists and update emailError accordingly:
    Future<void> checkEmailExists(String email) async {
      final emailInUse = await isEmailAlreadyInUse(email);
      setState(() {
        emailError = emailInUse ? 'Email is already in use' : null;
      });
    }
    
    1. Call checkEmailExists from the onFieldSubmitted or onEditingComplete callback of the email TextFormField:
    onEditingComplete: () => checkEmailExists(emailController.text),
    
    1. Update the validator for the email field to use the emailError state:
    validator: (_) => emailError,
    
    1. Before submitting the form, ensure the email has been checked:
    onPressed: () async {
      await checkEmailExists(emailController.text);
      if (formKey.currentState!.validate()) {
        // Proceed with submission
      }
    },
    

    This way, the email existence check occurs outside the validator, and the form only submits after the check is complete.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search