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

Time:04-01

I've been working on trying to build a login page with Flutter/Dart and BLoC. So far I've got a login page with two input fields for a username and password and a login button that is built off of rounded_loading_button.dart.

On first pressing the login button with nothing in either field, the appropriate error is returned in the snackbar and the button is reset.

However, the problem I'm having is that if you press the button a second time, the button continues to spin and the snackbar never shows again. It's as if there is no state being returned somehow.

I've stepped through the code, tried to reset the state but I've not found a solution. I've provided all the files below that I'm working with.

Setting LoginUserState.failed seems to only work once in this case and I can't seem to figure out how to reset it so a second attempt will show the snackbar again and any error and also reset the login button.

Main.dart file

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 file

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 file

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);
    }, one rror: (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 file

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

}
 

CodePudding user response:

Bloc will not rebuild its child if the state is the same as the previous state, in your case you yield the same state.

a simple solution would be to yield a different state before

emit(await _api.generateToken(email, password, env));

check this link for more details

  • Related