flutter - 由于堆叠屏幕 flutter ,Snackbar 显示两次,我该如何避免?

标签 flutter dart provider riverpod

我不想在堆叠屏幕上显示 Snackbar。当用户从登录屏幕点击“注册”时。然后,SignUpScreen 堆叠在 LoginScreen 之上。但问题是两者都实现了相同的 ProviderListener,因此,它多次显示 Snackbar。我怎样才能避免它? ,我怎样才能确保,如果当前路由是 SignUp 那么显示 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;
    }
  }
}

登录屏幕.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: "<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="91fcf0e2feffd1f6fcf0f8fdbff2fefc" rel="noreferrer noopener nofollow">[email protected]</a>"),
      _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);
}

最佳答案

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

使用此函数调用snackbar,它将删除当前的snackbar并仅显示最新的。

关于flutter - 由于堆叠屏幕 flutter ,Snackbar 显示两次,我该如何避免?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69330128/

相关文章:

flutter - 如何将 <dynamic> 转换为 List<String>?

flutter - 错误 : If the routerConfig is provided, 不得提供所有其他路由器委托(delegate)(GoRouting 包)

android - 如何使用 Setter 为另一个提供者类中的提供者类提供 flutter

flutter - Flutter Bloc Equatable状态,在所有状态类中具有一个共同属性

flutter - 如何获取ListTile中的行ID或行号?

flutter - 通过 MaterialPageRoute 导航

http - Flutter http 包不存在

flutter - 如果 widget 多次调用 InheritedWidget.of() 是否有任何性能问题?

dart - 在AngularDart中处理子组件时的特定设计问题

azure - 无法使用带有 SQL 的通用 session 状态提供程序跨实例访问 session 状态