Hammad Parveez
Hammad Parveez

Reputation: 346

Snackbar showing twice due to stacked screen Flutter, How can i avoid it?

I don't want to show Snackbar on stacked screen. When user taps on Signup from LoginScreen. Then, SignUpScreen stacked over LoginScreen. but the problem is both implements same ProviderListener and therefore, It shows Snackbar multiple times. How can I avoid it? , How can I make sure that, If current route is SignUp then show Snackbar

UserAuthService.dart

import 'package:notifications/domain/repository/firebase_repository/firebase_user_repo.dart';
import 'package:notifications/domain/services/auth_service/all_auth_builder.dart';
import 'package:notifications/export.dart';
import 'package:notifications/resources/local/local_storage.dart';

enum AuthenticationStatus {
  loading,
  error,
  success,
}

class UserAuthService extends ChangeNotifier {
  final authBuilder = AllTypeAuthBuilder();
  String? _errorMsg, _sessionID;
  AuthenticationStatus _status = AuthenticationStatus.loading;

  EmailLinkAuthenticationRepo? _repo;

  String? get errorMsg => this._errorMsg;

  void get _setDefault {
    _errorMsg = null;
    //_sessionID = null;
  }

  String? get sessionID {
    return LocallyStoredData.getSessionID();
  }

  void logOut() {
    return LocallyStoredData.deleteUserKey();
  }

  Future<bool> userExists(String userID) async {
    
    final isExists = await authBuilder.checkUserExists(userID);
    return isExists ? true : false;
  }

  Future<bool> login() async {
    _setDefault;
    try {
      await authBuilder.login();
      return true;
    } on BaseException catch (e) {
      log("Exception $e");
      _errorMsg = e.msg;
      notifyListeners();
      return false;
    }
  }

  Future<bool> register(String username, String email, String password) async {
    _setDefault;
    try {
      await authBuilder.register(username, email, password);
      return true;
    } on BaseException catch (e) {
      log("Exception $e");
      _errorMsg = e.msg;
      notifyListeners();
      return false;
    }
  }

  Future<bool> signIn(String userID, String password) async {
    _setDefault;
    try {
      await authBuilder.signIn(userID, password);
      return true;
    } on BaseException catch (e) {
      log("Exception ${e.msg}");
      _errorMsg = e.msg;
      notifyListeners();
    }
    return false;
  }

  void loginWithEmail(String email) async {
    _setDefault;
    try {
      _repo = await authBuilder.loginWithEmail(email);
      _repo!.onLinkListener(
        onSuccess: _onSuccess,
        onError: _onError,
      );
    } on BaseException catch (e) {
      log("Exception ${e.msg}");
      _errorMsg = e.msg;
    }
    notifyListeners();
  }

  Future<bool> _onSuccess(PendingDynamicLinkData? linkData) async {
    _setDefault;
    try {
      log("OnLinkAuthenticate");
      await _repo!.onLinkAuthenticate(linkData);
      return true;
    } on BaseException catch (e) {
      log("Error onSucess: $e");
      _errorMsg = e.msg;
      notifyListeners();
    }
    return false;
  }

  Future<dynamic> _onError(OnLinkErrorException? error) async {
    log("Error $error in Link");
  }

  Future<void> tryTo(Function callback) async {
    try {
      await callback();
    } on BaseException catch (e) {
      _errorMsg = e.msg;
    }
  }
}

LoginScreen.dart

class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _userIDController = TextEditingController(),
      _passwordController = TextEditingController();
  final _formKey = GlobalKey<FormState>();
  bool? isAuthenticated;

  @override
  initState() {
    super.initState();
  }

  @override
  void dispose() {
    _userIDController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  _onGoogleLogin() async {
    context.read(loginPod).login();
  }

  _onLoginButtonTap() {
    
    networkCheckCallback(context, () async {
      if (_formKey.currentState!.validate()) {
        WidgetUtils.showLoaderIndicator(context, 'Loading...');
        final isSignedIn = await context
            .read(loginPod)
            .signIn(_userIDController.text, _passwordController.text);
        Navigator.pop(context);
        if (isSignedIn) Beamer.of(context).beamToNamed(Routes.home);
      }
    });
  }

  _resetAuthenticateState() {
    if (isAuthenticated != null)
      setState(() {
        isAuthenticated = null;
      });
  }

  onUsernameChange(String? value) async {
    final error = await hasNetworkError();
    if (_userIDController.text.isNotEmpty && error == null) {
      isAuthenticated = await context.read(loginPod).userExists(value!);
      setState(() {});
      return;
    }
    _resetAuthenticateState();
  }

  onPasswordChange(String? value) {
    //Code goes here....
  }

  loginWith(BuildContext context, LoginType type) {
    switch (type) {
      case LoginType.emailLink:
        Beamer.of(context).beamToNamed(Routes.email_link_auth);
        break;
      case LoginType.idPassword:
        Beamer.of(context).beamToNamed(Routes.login_id_pass);
        break;
      case LoginType.googleAuth:
        Beamer.of(context).beamToNamed(Routes.login_with_google);
        break;
      case LoginType.unknown:
        Beamer.of(context).beamToNamed(Routes.register);
        break;
    }
  }

  Future<bool> _onBackPress(_) async {
    return await showDialog<bool>(
            context: _,
            builder: (context) {
              return AlertDialog(
                title: Text("Do you want to exit?"),
                actions: [
                  TextButton(
                      onPressed: () => Navigator.of(context).pop(true),
                      child: Text("OK")),
                  TextButton(
                      onPressed: () {
                        Beamer.of(_).popRoute();
                      },
                      child: Text("Cancel"))
                ],
              );
            }) ??
        false;
  }

  _onLoginStatus(BuildContext _, UserAuthService service) {
    if (service.errorMsg != null)
      _.showErrorBar(
          content: Text(
        "WithLogin" + service.errorMsg!,
        style: TextStyle(fontSize: 12.sp),
      ));
  }

  @override
  Widget build(BuildContext _) {
    return ProviderListener(
      onChange: _onLoginStatus,
      provider: loginPod,
      child: WillPopScope(
        onWillPop: () => _onBackPress(_),
        child: Scaffold(
          body: SingleChildScrollView(
              child: SizedBox(height: 1.sh, child: _buildLoginScreen())),
        ),
      ),
    );
  }

  Widget _buildLoginScreen() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        //_buildVrtSpacer(60),
        _buildHeading(),
        //_buildVrtSpacer(30),
        _buildForm(),
        //_buildVrtSpacer(30),
        _buildIconButtons(),

        _buildSignUpButton(),
      ],
    );
  }

  BoldHeadingWidget _buildHeading() =>
      BoldHeadingWidget(heading: AppStrings.login);

  ResponsiveVrtSpacer _buildVrtSpacer(double value) =>
      ResponsiveVrtSpacer(space: value);

  Widget _buildForm() {
    return CustomForm(
      formKey: _formKey,
      child: Column(
        children: [
          _buildUsernameField(),
          _buildVrtSpacer(10),
          _buildPasswordField(),
          _buildForgetPassword(),
          _buildLoginButton(),
        ],
      ),
    );
  }

  Widget _buildPasswordField() {
    return CustomTextFieldWithLabeled(
        controller: _passwordController,
        label: AppStrings.password,
        hintText: AppStrings.password,
        onValidate: (String? value) =>
            (value!.isEmpty) ? AppStrings.emptyPasswordMsg : null,
        obscureText: true,
        onChange: onPasswordChange,
        icon: CupertinoIcons.lock);
  }

  Widget _buildUsernameField() {
    return CustomTextFieldWithLabeled(
        controller: _userIDController,
        label: AppStrings.usernameOrEmail,
        hintText: AppStrings.usernameOrEmail1,
        icon: CupertinoIcons.person,
        onChange: onUsernameChange,
        onValidate: (String? value) =>
            (value!.isEmpty) ? AppStrings.emptyUserIDMsg : null,
        suffixIcon: isAuthenticated == null
            ? null
            : (isAuthenticated!
                ? CupertinoIcons.checkmark_alt_circle_fill
                : CupertinoIcons.clear_circled_solid),
        suffixColor: isAuthenticated == null
            ? null
            : (isAuthenticated! ? Colors.green : Styles.defaultColor));
  }

  Widget _buildIconButtons() {
    return Column(
      children: [
        Text("Or", style: TextStyle(fontSize: 14.sp)),
        const SizedBox(height: 10),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const SizedBox(width: 10),
            _buildIconButton(
                iconPath: 'assets/icons/email-icon.svg', onTap: () {}),
            const SizedBox(width: 8),
            _buildIconButton(
                iconPath: 'assets/icons/icons8-google.svg',
                onTap: _onGoogleLogin),
          ],
        ),
      ],
    );
  }

  Widget _buildIconButton(
      {required String iconPath, required VoidCallback onTap}) {
    return GestureDetector(
        onTap: onTap,
        child: SvgPicture.asset(
          iconPath,
          height: 30.sp,
          width: 30.sp,
        ));
  }

  Widget _buildSignUpButton() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.end,
      children: [
        Text(
          AppStrings.dontHaveAccount,
          style: TextStyle(color: Colors.black54, fontSize: 14.sp),
        ),
        const SizedBox(height: 5),
        CustomTextButton(
            title: "Sign Up",
            onPressed: () => Beamer.of(context).beamToNamed(Routes.register)),
      ],
    );
  }

  Widget _buildLoginButton() {
    return DefaultElevatedButton(
      onPressed: _onLoginButtonTap,
      title: AppStrings.login,
    );
  }

  Widget _buildForgetPassword() {
    return Align(
      alignment: Alignment.centerRight,
      child: TextButton(
        style: ButtonStyle(
          overlayColor: MaterialStateProperty.all(Color(0x11000000)),
          foregroundColor: MaterialStateProperty.all(Color(0x55000000)),
        ),
        onPressed: () {},
        child: Text("Forget Password?"),
      ),
    );
  }
}

SignUpScreen.dart

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

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

class _SignUpState extends State<SignUp> {
  final _formKey = GlobalKey<FormState>();
  final _usernameController = TextEditingController(text: "hammad11"),
      _emailController = TextEditingController(text: "[email protected]"),
      _passwordController = TextEditingController(text: "ha11"),
      _confirmPassController = TextEditingController(text: "ha11");

  _onValidate(String? value, ValidationType type) {
    switch (type) {
      case ValidationType.username:
        if (value!.isNotEmpty && value.length < 8)
          return "Username Must Be 8 characters long";
        else if (value.isEmpty) return "Username required";
        return null;
      case ValidationType.email:
        if (value!.isEmpty)
          return "Email required";
        else if (!value.isEmail) return "Please enter a Valid Email";
        return null;
      case ValidationType.password:
        if (value!.isEmpty)
          return "Password required";
        else if (value.isAlphabetOnly || value.isNumericOnly)
          return "Password must be AlphaNumeric";
        return null;
      case ValidationType.confirmPassword:
        if (value!.isEmpty)
          return "Confirm Password required";
        else if (value != _passwordController.text)
          return "Password doesn't match";
        return null;
    }
  }

  _onRegister() async {
    //Clears any snackbar opened due to Error or Multiple clicks
    //ScaffoldMessenger.of(context).clearSnackBars();
    log("SignUp -> _onRegisterTap ");
    

    if (_formKey.currentState!.validate()) {
      WidgetUtils.showLoaderIndicator(context, "Please wait! Loading.....");
      final isLoggedIn = await context.read(loginPod).register(
            _usernameController.text,
            _emailController.text,
            _passwordController.text,
          );
      await Beamer.of(context).popRoute();
      if (isLoggedIn) Beamer.of(context).beamToNamed(Routes.login);
    } else
      log("Form Input Invalid");
  }

  _onChanged(_, UserAuthService service) async {
    if (service.errorMsg != null) WidgetUtils.snackBar(_, service.errorMsg!);

    // if (!service.isLoading) await Beamer.of(context).popRoute();
    // if (service.taskCompleted) {
    //   log("User Added Successfully");
    //   Beamer.of(context).popToNamed(
    //     Routes.login_id_pass,
    //     replaceCurrent: true,
    //   );
    // } else {
    //   WidgetUtils.snackBar(context, service.errorMsg!);
    // }
  }

  _alreadyHaveAccount() => Beamer.of(context).popToNamed(Routes.main);

  @override
  Widget build(BuildContext context) {
    return ProviderListener(
      provider: loginPod,
      onChange: _onChanged,
      child: Scaffold(
        body: LayoutBuilder(builder: (context, constraints) {
          return SingleChildScrollView(
            child: SizedBox(
              height: 1.sh,
              child: Column(
                children: [
                  _buildSpacer(50),
                  BoldHeadingWidget(heading: "Sign Up"),
                  CustomForm(
                    child: Column(
                      children: [
                        CustomTextFieldWithLabeled(
                            label: "Username",
                            hintText: "Type Username",
                            onValidate: (value) =>
                                _onValidate(value, ValidationType.username),
                            controller: _usernameController,
                            icon: CupertinoIcons.person),
                        CustomTextFieldWithLabeled(
                            label: "Email",
                            hintText: "Type Email",
                            controller: _emailController,
                            onValidate: (value) =>
                                _onValidate(value, ValidationType.email),
                            icon: CupertinoIcons.envelope),
                        CustomTextFieldWithLabeled(
                            label: "Password",
                            hintText: "Type Password",
                            controller: _passwordController,
                            onValidate: (value) =>
                                _onValidate(value, ValidationType.password),
                            icon: CupertinoIcons.lock),
                        CustomTextFieldWithLabeled(
                            label: "Confirm Password",
                            hintText: "Type Confirm Password",
                            onValidate: (value) => _onValidate(
                                value, ValidationType.confirmPassword),
                            controller: _confirmPassController,
                            icon: CupertinoIcons.lock),
                        Row(
                          children: [
                            Checkbox(
                              value: true,
                              onChanged: (value) {},
                              materialTapTargetSize:
                                  MaterialTapTargetSize.shrinkWrap,
                            ),
                            Flexible(
                              child: Text(
                                "I accept Terms & Conditions and the Privacy Policy",
                                style: TextStyle(fontSize: 13.sp),
                              ),
                            ),
                          ],
                        ),
                        _buildSpacer(10),
                        DefaultElevatedButton(
                          title: "Sign Up",
                          onPressed: _onRegister,
                        ),
                      ],
                    ),
                    formKey: _formKey,
                  ),
                  const SizedBox(height: 10),
                  CustomTextButton(
                    onPressed: () => Beamer.of(context)
                        .popToNamed(Routes.login, stacked: false),
                    title: AppStrings.alreadyHaveAccount,
                  ),
                  // Spacer(flex: 2),
                ],
              ),
            ),
          );
        }),
      ),
    );
  }

  ResponsiveVrtSpacer _buildSpacer(double value) =>
      ResponsiveVrtSpacer(space: value.h);
}

Upvotes: 3

Views: 5153

Answers (1)

Wali Khan
Wali Khan

Reputation: 652

showsnackbar(String message, context) {
final snackbar = SnackBar(
  content: Text(message),
  duration: Duration(seconds: 2),
);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(snackbar);
 }

Use this function to call snackbar it will remove the current snackbar and will only show the latest.

Upvotes: 4

Related Questions