iOS Development

android – Easy methods to logout in Flutter utilizing BLoC supplier and redirect to login display?

Spread the love


I’m fairly new to the BLoC supplier and that is the primary greater challenge the place I implement the supplier. To offer you a brief overview of the challenge, it has a login display, after efficiently logging in – it redirects you to the ‘Reservations’ display the place you’ll be able to view all of your reservations.

I’m utilizing retrofit for the API calls, the authentication is made with Password Grant kind (OAuth), that means it returns entry and refresh token when the login particulars are right.

I applied an interceptor so when the entry token is expired, it fires a brand new API name to the /oauth/refresh with the refreshToken from storage in an effort to acquire a brand new entry and refresh tokens.

What I wish to obtain is the next state of affairs: if the entry token is expired, then it tries to acquire new one by passing the refresh token, nonetheless if that’s expired too then it return an error (401 Unauthenticated) – if this state of affairs occurs I wish to logout the consumer from the app and redirect them to the /login display.

I’ve applied it, it efficiently removes the consumer from the safe storage, nonetheless, the display is caught on the ‘Reservations’ display with round progress indicator loading, slightly than navigating to the login display.

This is a code breakdown:

These are my three screens.
primary.dart

last GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

void primary() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeDependencies();
  runApp(MyApp(
    appRouter: AppRouter(),
  ));
}

class MyApp extends StatelessWidget {
  last AppRouter appRouter;

  const MyApp({Key? key, required this.appRouter}) : tremendous(key: key);

  @override
  Widget construct(BuildContext context) {
    return ScreenUtilInit(
        designSize: const Dimension(430, 932),
        minTextAdapt: true,
        splitScreenMode: true,
        builder: (_, baby) {
          return MultiBlocProvider(
            suppliers: [
              BlocProvider<AuthBloc>(
                create: (context) =>
                    sl<AuthBloc>()..add(const CheckAuthentication()),
              ),
              BlocProvider<ReservationsFindAllBloc>(
                create: (context) => sl<ReservationsFindAllBloc>(),
              ),
            ],
            baby: MaterialApp(
              theme: ThemeData(),
              residence: BlocListener<AuthBloc, AuthState>(
                listener: (context, state) {
                  if (state is Authenticated) {
                    navigatorKey.currentState!.pushNamedAndRemoveUntil('/reservations', (route) => false);
                  } else if (state is Unauthenticated) {
                    navigatorKey.currentState!.pushNamedAndRemoveUntil('/login', (route) => false);
                  }
                },
                baby: Navigator(
                  key: navigatorKey,
                  onGenerateRoute: AppRouter().onGenerateRoute,
                ),
              ),
            ),
          );
        });
  }
}

login.dart

class LoginScreen extends StatefulWidget {
  const LoginScreen({tremendous.key});

  @override
  State<StatefulWidget> createState() {
    return _LoginScreenState();
  }
}

class _LoginScreenState extends State<LoginScreen> {
  last _formKey = GlobalKey<FormState>();

  last TextEditingController _usernameController = TextEditingController();
  last TextEditingController _passwordController = TextEditingController();
  last FocusNode _usernameFocusNode = FocusNode();
  last FocusNode _passwordFocusNode = FocusNode();

  @override
  void dispose() {
    _usernameController.dispose();
    _passwordController.dispose();
    _usernameFocusNode.dispose();
    _passwordFocusNode.dispose();
    tremendous.dispose();
  }

  @override
  Widget construct(BuildContext context) {
    return Scaffold(
        backgroundColor: colorGrayLighter,
        physique: BlocBuilder<AuthBloc, AuthState>(
          builder: (context, state) {
            if (state is LoadingState) {
              return const Heart(
                baby: CircularProgressIndicator(),
              );
            } else {
              return Padding(
                padding: const EdgeInsets.all(10),
                baby: Container(
                  margin: EdgeInsets.solely(
                      prime: MediaQuery.of(context).viewPadding.prime + 0.1.sh),
                  baby: Column(
                    youngsters: [
                      Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Image.asset(
                            'assets/images/logo.png',
                            height: 0.06.sh,
                            fit: BoxFit.contain,
                          ),
                        ],
                      ),
                      Kind(
                        key: _formKey,
                        baby: Container(
                            width: double.infinity,
                            margin: EdgeInsets.solely(prime: 0.07.sh),
                            baby: Column(
                              youngsters: [
                                TextFieldWidget(
                                  textInputType: TextInputType.text,
                                  defaultText: 'Корисничко име',
                                  hintText: 'Корисничко име',
                                  focusNode: _usernameFocusNode,
                                  controller: _usernameController,
                                ),
                                SizedBox(
                                  height: 20.sp,
                                ),
                                TextFieldWidget(
                                  textInputType: TextInputType.text,
                                  defaultText: 'Лозинка',
                                  hintText: 'Лозинка',
                                  obscureText: true,
                                  focusNode: _passwordFocusNode,
                                  controller: _passwordController,
                                ),
                              ],
                            )),
                      ),
                      SizedBox(
                        top: 30.sp,
                      ),
                      CommonButton(
                        buttonText: 'Најави се',
                        buttonType: ButtonType.FilledRed,
                        onTap: () {
                          BlocProvider.of<AuthBloc>(context).add(LoginEvent(
                              LoginDto(
                                  username: _usernameController.textual content,
                                  password: _passwordController.textual content)));
                        },
                      )
                    ],
                  ),
                ),
              );
            }
          },
        ));
  }
}

reservations.dart

class ReservationsScreen extends StatefulWidget {
  const ReservationsScreen({Key? key}) : tremendous(key: key);

  @override
  State<ReservationsScreen> createState() => _ReservationsScreenState();
}

class _ReservationsScreenState extends State<ReservationsScreen> {
  void showAlert(String message) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Textual content("Error"),
          content material: Textual content(message),
          actions: <Widget>[
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: const Text("OK"),
            ),
          ],
        );
      },
    );
  }

  @override
  void initState() {
    tremendous.initState();

    BlocProvider.of<ReservationsFindAllBloc>(context)
        .add(const GetReservationsFindAll());
  }

  @override
  Widget construct(BuildContext context) {
    return Scaffold(
      backgroundColor: colorGrayLighter,
      physique: Padding(
        padding: const EdgeInsets.all(10),
        baby: Column(
          crossAxisAlignment: CrossAxisAlignment.begin,
          youngsters: [
            SizedBox(
              height: MediaQuery
                  .of(context)
                  .viewPadding
                  .top,
            ),
            Text("Резервации", style: font28Medium),
            Container(
              height: 40.h,
              padding: const EdgeInsets.only(top: 8),
              child: ListView(
                scrollDirection: Axis.horizontal,
                children: [
                  Padding(
                      padding: const EdgeInsets.only(right: 8),
                      child: OutlinedButton(
                          onPressed: () {
                            BlocProvider.of<ReservationsFindAllBloc>(context)
                                .add(GetReservationsFindAll(
                                reservationsFindAllDto:
                                ReservationsFindAllDto(
                                    filterReservationsEnum:
                                    FilterReservationsEnum.ALL)));
                          },
                          style: OutlinedButton.styleFrom(
                            padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
                            side: const BorderSide(
                                color: colorBlack), // Set border color
                          ),
                          child: Text(
                            'Сите (1029)',
                            style: GoogleFonts.montserrat(
                                fontWeight: FontWeight.w500,
                                fontSize: 12,
                                textStyle: const TextStyle(
                                  color: colorBlack,
                                )),
                          ))),
                  Padding(
                      padding: const EdgeInsets.only(right: 8),
                      child: OutlinedButton(
                          onPressed: () {
                            BlocProvider.of<ReservationsFindAllBloc>(context)
                                .add(GetReservationsFindAll(
                                reservationsFindAllDto:
                                ReservationsFindAllDto(
                                    filterReservationsEnum:
                                    FilterReservationsEnum
                                        .NOT_APPROVED)));
                          },
                          style: OutlinedButton.styleFrom(
                            minimumSize: Size.zero,
                            padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
                            side: const BorderSide(
                                color: colorGrayLight), // Set border color
                          ),
                          child: Text(
                            'Непотврдени (1029)',
                            style: GoogleFonts.montserrat(
                                fontWeight: FontWeight.normal,
                                fontSize: 12,
                                textStyle: const TextStyle(
                                  color: colorGrayLight,
                                )),
                          ))),
                  Padding(
                      padding: const EdgeInsets.only(right: 8),
                      child: OutlinedButton(
                          onPressed: () {
                            BlocProvider.of<ReservationsFindAllBloc>(context)
                                .add(GetReservationsFindAll(
                                reservationsFindAllDto:
                                ReservationsFindAllDto(
                                    filterReservationsEnum:
                                    FilterReservationsEnum
                                        .APPROVED)));
                          },
                          style: OutlinedButton.styleFrom(
                            minimumSize: Size.zero,
                            padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
                            side: const BorderSide(
                                color: colorGrayLight), // Set border color
                          ),
                          child: Text(
                            'Потврдени (1029)',
                            style: GoogleFonts.montserrat(
                                fontWeight: FontWeight.normal,
                                fontSize: 12,
                                textStyle: const TextStyle(
                                  color: colorGrayLight,
                                )),
                          ))),
                ],
              ),
            ),
            // Different widgets...
            Expanded(
              baby: BlocListener<AuthBloc, AuthState>(
                listener: (context, state) {
                  if (state is Unauthenticated) {
                    Navigator.pushReplacementNamed(context, '/login');
                  }
                },
                baby: BlocBuilder<ReservationsFindAllBloc,
                    ReservationsFindAllState>(
                  builder: (_, state) {
                    if (state is ReservationsLoading) {
                      return const Heart(
                        baby: CircularProgressIndicator(),
                      );
                    }

                    if (state is ReservationsLoaded) {
                      return ReservationsList(
                        reservationListEntity: state.reservationListEntity!,
                      );
                    }

                    if (state is ReservationsError) {
                      Future.microtask(() {
                        showAlert(Utils.getErrorResponseMessage(
                            state.exception!.response!));
                      });
                    }

                    return const SizedBox();
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

These are the Blocs:
reservations_find_all_bloc.dart

class ReservationsFindAllBloc extends Bloc<ReservationsFindAllEvent, ReservationsFindAllState> {
  last FindAllUseCase _findAllUseCase;

  ReservationsFindAllBloc(this._findAllUseCase) : tremendous(const ReservationsLoading()) {
    on<GetReservationsFindAll>(onFindAll);
  }

  void onFindAll(GetReservationsFindAll getReservationsFindAll, Emitter<ReservationsFindAllState> emit) async {
    emit(const ReservationsLoading());

    last dataState = await _findAllUseCase.name(params: getReservationsFindAll.reservationsFindAllDto);

    if (dataState is DataSuccess) {
      emit(ReservationsLoaded(dataState.information!));
    }

    if (dataState is DataFailed) {
      emit(ReservationsError(dataState.exception!));
    }
  }
}

auth_bloc.dart

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  last LoginUseCase _loginUseCase;
  last LogoutUseCase _logoutUseCase;
  last SecureStorage _secureStorage;

  AuthBloc(this._loginUseCase, this._logoutUseCase, this._secureStorage)
      : tremendous(const Unauthenticated()) {
    on<LoginEvent>(onLogin);
    on<LogoutEvent>(onLogout);
    on<CheckAuthentication>(onCheckAuthentication);
  }

  void onLogin(LoginEvent loginEvent, Emitter<AuthState> emit) async {
    emit(const LoadingState());
    last dataState = await _loginUseCase(params: loginEvent.loginDto);

    if (dataState is DataSuccess) {
      emit(Authenticated(dataState.information!));
    }

    if (dataState is DataFailed) {
      emit(AuthenticationError(dataState.exception!));
    }
  }

  void onLogout(LogoutEvent logoutEvent, Emitter<AuthState> emit) async {
    emit(const LoadingState());

    last isLoggedOut = await _logoutUseCase.name();

    if (isLoggedOut) {
      emit(const Unauthenticated());
    }
  }

  void onCheckAuthentication(CheckAuthentication checkAuthentication,
      Emitter<AuthState> emit) async {
    last consumer = await _secureStorage.getUser();

    if (consumer != null) {
      emit(Authenticated(consumer));
    } else {
      emit(const Unauthenticated());
    }
  }
}

and that is the interceptor:

If the comply with assertion is true else if (response is DataFailed) then the consumer needs to be logged out and brought to the /login display.

auth_interceptor.dart

class AuthInterceptor extends Interceptor {
  last TokenRepository _tokenRepository;
  last SecureStorage _secureStorage;
  last AuthBloc authBloc;
  last _dio = sl<Dio>();

  AuthInterceptor(this._secureStorage, this._tokenRepository, this.authBloc);

  @override
  void onRequest(
      RequestOptions choices, RequestInterceptorHandler handler) async {
    print('Knowledge for request...');
    print(choices.information);
    choices.headers['Accept'] = 'utility/json';
    if (choices.headers.containsKey('Content material-Sort')) {
      choices.headers['Content-Type'] = 'utility/json';
    }

    if (!choices.further.containsKey('isRetry')) {
      last accessToken = await _secureStorage.getAccessToken();

      if (accessToken != null) {
        choices.headers['Authorization'] = 'Bearer $accessToken';
      }
    }

    return handler.subsequent(choices);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    if (err.response?.statusCode == 401 &&
        err.response?.information['error'] == 'Token expired.') {
      attempt {
        last refreshToken = await _secureStorage.getRefreshToken();

        if (refreshToken != null) {
          last response = await _tokenRepository.refresh(
            RefreshTokenDto(refreshToken: refreshToken),
          );

          if (response is DataSuccess) {
            // Retry the unique request with new entry token
            last RequestOptions retryOptions = err.requestOptions;
            retryOptions.further['isRetry'] = true; // Set flag to point retry
            retryOptions.headers['Authorization'] =
                'Bearer ${response.information!.accessToken}';
            // Resend the request with up to date choices
            last updatedResponse = await _dio.request(
              retryOptions.uri.toString(),
              choices: Choices(
                technique: retryOptions.technique,
                headers: retryOptions.headers,
                responseType: retryOptions.responseType,
              ),
              information: retryOptions.information,
              queryParameters: retryOptions.queryParameters,
              cancelToken: retryOptions.cancelToken,
              onReceiveProgress: retryOptions.onReceiveProgress,
              onSendProgress: retryOptions.onSendProgress,
            );

            // Ahead the response to the unique handler
            return handler.resolve(updatedResponse);
          } else if (response is DataFailed) {
            // Logout consumer if refresh token fails
            authBloc.add(const LogoutEvent());
            return;
          }
        }
      } catch (e) {
        print('Error refreshing tokens: $e');
      }
      return handler.subsequent(err);
    }

    tremendous.onError(err, handler);
  }
}

Be happy to let me know if I’ve tousled someplace with the Bloc Suppliers, Listeners, or Builders all through the app as a result of I’m nonetheless new to this idea.

Thanks quite a bit!

Leave a Reply

Your email address will not be published. Required fields are marked *