使用 Futter/Dart/BLoC 第二次按下后登录按钮不会重置状态

Login button will not reset state after second press with Futter/Dart/BLoC

我一直在尝试使用 Flutter/Dart 和 BLoC 构建登录页面。到目前为止,我有一个登录页面,其中包含两个用户名和密码输入字段以及一个由 rounded_loading_button.dart.

构建的登录按钮

在任何一个字段都没有任何内容的情况下首次按下登录按钮时,信息栏中会返回相应的错误,并且按钮会被重置。

但是,我遇到的问题是,如果您再次按下该按钮,该按钮将继续旋转,并且小吃栏再也不会显示。就好像没有以某种方式返回任何状态。

我已经检查了代码,尝试重置状态,但我没有找到解决方案。我在下面提供了我正在使用的所有文件。

在这种情况下,设置 LoginUserState.failed 似乎只起作用一次,我似乎无法弄清楚如何重置它,所以第二次尝试将再次显示小吃栏和任何错误,并重置登录按钮.

Main.dart 文件

import 'package:auto_orientation/auto_orientation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:/screens/login.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  AutoOrientation.portraitAutoMode();
  runApp(TestLoginApp());
}

class TestLoginApp extends StatelessWidget {
  TestLoginApp({Key? key}) : super(key: key);

  final _router = GoRouter(
    routes: [
      GoRoute(
        name: 'login',
        path: '/login',
        builder: (context, state) => LoginPage(),
      ),
    ],
  );

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) => MaterialApp.router(
        routeInformationParser: _router.routeInformationParser,
        routerDelegate: _router.routerDelegate,
        title: 'Login test with BLoC',
        theme: ThemeData(
          backgroundColor: Colors.black,
        ),
      );
}
 

Login.dart 文件

import 'package:auto_orientation/auto_orientation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:rounded_loading_button/rounded_loading_button.dart';
import 'package:/services/api.dart';

import '../cubit/login_user_cubit.dart';
import '../singleton.dart';

class LoginPage extends StatefulWidget {
  LoginPage({Key, key, this.title}) : super(key: key);

  final String? title;

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

class _LoginPageState extends State<LoginPage> {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => LoginUserCubit(LoginUserState.initial),
      child: LoginWidget(),
    );
  }
}

class LoginWidget extends StatefulWidget {
  @override
  _LoginWidgetState createState() => _LoginWidgetState();
}

class _LoginWidgetState extends State<LoginWidget> {
  TextStyle style = const TextStyle(fontSize: 15.0);
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final RoundedLoadingButtonController _btnController =
      RoundedLoadingButtonController();

  final _scaffoldKey = GlobalKey<ScaffoldState>();
  final _singleton = Singleton();
  bool _waiting = false;
  bool _buttonEnable = false;
  String loginError = "";

  @override
  void initState() {
    super.initState();
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
    ]);
  }

  @override
  Widget build(BuildContext context) {

    List<Widget> _buildPage(BuildContext context) {
      final loginButton = RoundedLoadingButton(
          controller: _btnController,
          color: Theme.of(context).primaryColor,
          onPressed: () => {
                setState(() {
                  _waiting = true;
                }),
                context.read<LoginUserCubit>().loginUser(
                    email: _emailController.text,
                    password: _passwordController.text,
                    env: _singleton.env)
              },
          child: const Text(
            "Login now",
            textAlign: TextAlign.center,
            style: TextStyle(color: Colors.white),
          ));

      final emailField = TextField(
          controller: _emailController,
          obscureText: false,
          autocorrect: false,
          enableSuggestions: false,
          autofocus: true,
          keyboardType: TextInputType.emailAddress,
          style: const TextStyle(fontSize: 16),
          decoration: InputDecoration(
            filled: true,
            hintText: "Email",
            fillColor:
                _singleton.error != "" ? const Color(0xFFFFD5D5) : Colors.white,
            contentPadding: const EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
            focusedBorder: const OutlineInputBorder(
                borderSide: BorderSide(color: Colors.white),
                borderRadius: BorderRadius.all(Radius.circular(0))),
            enabledBorder: const OutlineInputBorder(
                borderSide: BorderSide(color: Colors.white),
                borderRadius: BorderRadius.all(Radius.circular(0))),
          ),
          onChanged: (e) {
            setState(() {
              _singleton.error = "";
              _buttonEnable = e.isNotEmpty;
            });
          });

      final passwordField = TextField(
          controller: _passwordController,
          obscureText: true,
          style: const TextStyle(fontSize: 16),
          decoration: InputDecoration(
            filled: true,
            hintText: "Password",
            fillColor:
                _singleton.error != "" ? const Color(0xFFFFD5D5) : Colors.white,
            contentPadding: const EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
            focusedBorder: const OutlineInputBorder(
                borderSide: BorderSide(color: Colors.white),
                borderRadius: BorderRadius.all(Radius.circular(0))),
            enabledBorder: const OutlineInputBorder(
                borderSide: BorderSide(color: Colors.white),
                borderRadius: BorderRadius.all(Radius.circular(0))),
          ),
          onChanged: (e) {
            setState(() {
              _buttonEnable = e.isNotEmpty;
            });
          });

      
      List<Widget> eList = [];
      eList.add(Text(loginError,
          style: const TextStyle(fontSize: 10, color: Color(0xFFFFD5D5))));


      var page = SingleChildScrollView(
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(40.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.center,
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                const SizedBox(height: 55.0),
                Image.asset(
                  "assets/logo.png",
                  width: 200,
                ),
                const SizedBox(height: 44.0),
                emailField,
                const SizedBox(height: 25),
                passwordField,
                const SizedBox(height: 25),
                Flexible(
                    child: Align(
                        alignment: FractionalOffset.bottomCenter,
                        child:
                            Column(children: [loginButton]))),
                const SizedBox(height: 20),
              ],
            ),
          ),
        ),
      );

      var l = <Widget>[];
      l.add(page);

      if (_waiting) {
        var modal = Stack(
          children: const [
            Opacity(
              opacity: 0.3,
              child: ModalBarrier(dismissible: false, color: Colors.grey),
            )
          ],
        );
        l.add(modal);
      }

      return l;
    }

    return BlocConsumer<LoginUserCubit, LoginUserState>(
        listener: (context, state) {
      switch (state) {
        case LoginUserState.success:
          // We can redirect the user somewhere else
          break;
        case LoginUserState.failed:
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              backgroundColor: Colors.red,
              content: Text('Login failed'),
            ),
          );
          _btnController.reset();
          break;
        case LoginUserState.initial:
          _waiting = false;
          _btnController.reset();
          break;
      }
    }, builder: (context, state) {
      print(state);
      return Scaffold(
          key: _scaffoldKey,
          backgroundColor: Colors.black,
          body: Stack(children: _buildPage(context)));
    });
  }
}

 

Api.dart 文件

import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:/cubit/login_user_cubit.dart';
import 'package:/services/auth_registration_listener.dart';
import 'package:/functions/save_current_login.dart';
import 'package:/models/login_error.dart';
import 'package:/models/user.dart';
import '../singleton.dart';

const baseUrl = "https://localwebsite.com";

class TheAPI {
  var api = Dio();
  String? accessToken;
  final _storage = const FlutterSecureStorage();
  final _singleton = Singleton();
  final env = "test";

  TheAPI() {
    api.interceptors
        .add(InterceptorsWrapper(onRequest: (options, handler) async {
      if (!options.path.contains('http')) {
        options.path = baseUrl + options.path;
      }
      options.headers['Authorization'] = 'Bearer $accessToken';
      return handler.next(options);
    }, onError: (DioError error, handler) async {
      if ((error.response?.statusCode == 401 &&
          error.response?.data['message'] == "Invalid JWT")) {
        if (await _storage.containsKey(key: 'refreshToken')) {
          if (await refreshToken()) {
            return handler.resolve(await _retry(error.requestOptions));
          }
        }
      }
      LoginUserState.failed;
      return handler.next(error);
    }));
  }

  Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
    final options = Options(
      method: requestOptions.method,
      headers: requestOptions.headers,
    );
    return api.request<dynamic>(requestOptions.path,
        data: requestOptions.data,
        queryParameters: requestOptions.queryParameters,
        options: options);
  }

  

  Future<LoginUserState> generateToken(
      String email, String password, String env) async {
    const uri = baseUrl + "/account/generateToken";
    Map<String, String> body = {
      'email': email,
      'password': password,
    };

    final b = json.encode(body);

    try {
      final response = await api.post(uri, data: b);
      final responseJson = response.data;
      _singleton.token = User.fromJson(responseJson).token;
      _singleton.refreshToken = User.fromJson(responseJson).refreshToken;
      _singleton.email = email;
      _singleton.passwd = password;
      _singleton.env = env;

      await clearCurrentLogin();
      await saveCurrentLogin(email, password, env, "false", responseJson);

      return LoginUserState.success;
    } on DioError catch (e) {
      if (e.response?.statusCode == 400 || e.response?.statusCode == 404) {
        _singleton.error = "Invalid Email or Password";
        return LoginUserState.failed;
      }
      return LoginUserState.failed;
    }
  }

  Future<bool> refreshToken() async {
    final refreshToken = await _storage.read(key: 'refreshToken');
    final response = await api.post(baseUrl + '/api/account/refreshToken',
        data: {'refreshToken': refreshToken});

    if (response.statusCode == 201) {
      accessToken = response.data;
      return true;
    } else {
      // refresh token is wrong
      accessToken = null;
      _storage.deleteAll();
      return false;
    }
  }

  Future<bool> changePassword(
      String email, String password, String repeatPassword) async {
    const uri = baseUrl + "/api/account/resetPassword";

    Map<String, String> body = {
      'email': email,
      'password': password,
      'repeatPassword': repeatPassword,
    };

    final b = json.encode(body);
    final response = await api.post(
      uri,
      data: b,
    );

    if (response.statusCode == 200) {
      final responseJson = response.data;

      _singleton.token = User.fromJson(responseJson).token;

      _singleton.refreshToken = User.fromJson(responseJson).refreshToken;

      await saveCurrentLogin(email, password, env, "false", responseJson);

      return true;
    }
    return false;
  }
} 

Login_user_cubit.dart 文件

import 'package:bloc/bloc.dart';
import 'package:/services/api.dart';
import '../services/auth_registration_listener.dart';

enum LoginUserState { success, failed, initial }

class LoginUserCubit extends Cubit<LoginUserState> implements AuthListener {
  LoginUserCubit(LoginUserState initialState) : super(initialState);

  final _api = TheAPI();

  void loginUser(
          {required String email,
          required String password,
          required String env}) async =>
      emit(await _api.generateToken(email, password, env));

  @override
  void failed() {
    emit(LoginUserState.initial);
    emit(LoginUserState.failed);
  }

}
 

如果状态与之前的状态相同,Bloc 将不会重建其子状态,在您的情况下,您会产生相同的状态。

一个简单的解决方案是在

之前产生不同的状态
emit(await _api.generateToken(email, password, env));

查看此 link 了解更多详情