3.5 C
London
Monday, February 12, 2024

android – The way to logout in Flutter utilizing BLoC supplier and redirect to login display?


I’m fairly new to the BLoC supplier and that is the primary larger venture the place I implement the supplier. To provide you a brief overview of the venture, 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 carried out 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 so as to get hold of a brand new entry and refresh tokens.

What I need 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, nevertheless if that’s expired too then it return an error (401 Unauthenticated) – if this state of affairs occurs I want to logout the person from the app and redirect them to the /login display.

I’ve carried out it, it efficiently removes the person from the safe storage, nevertheless, the display is caught on the ‘Reservations’ display with round progress indicator loading, reasonably than navigating to the login display.

This is a code breakdown:

These are my three screens.
fundamental.dart

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

void fundamental() 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 Measurement(430, 932),
        minTextAdapt: true,
        splitScreenMode: true,
        builder: (_, little one) {
          return MultiBlocProvider(
            suppliers: [
              BlocProvider<AuthBloc>(
                create: (context) =>
                    sl<AuthBloc>()..add(const CheckAuthentication()),
              ),
              BlocProvider<ReservationsFindAllBloc>(
                create: (context) => sl<ReservationsFindAllBloc>(),
              ),
            ],
            little one: 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);
                  }
                },
                little one: 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 Middle(
                little one: CircularProgressIndicator(),
              );
            } else {
              return Padding(
                padding: const EdgeInsets.all(10),
                little one: Container(
                  margin: EdgeInsets.solely(
                      prime: MediaQuery.of(context).viewPadding.prime + 0.1.sh),
                  little one: Column(
                    youngsters: [
                      Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Image.asset(
                            'assets/images/logo.png',
                            height: 0.06.sh,
                            fit: BoxFit.contain,
                          ),
                        ],
                      ),
                      Type(
                        key: _formKey,
                        little one: Container(
                            width: double.infinity,
                            margin: EdgeInsets.solely(prime: 0.07.sh),
                            little one: 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(
                        peak: 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),
        little one: 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(
              little one: BlocListener<AuthBloc, AuthState>(
                listener: (context, state) {
                  if (state is Unauthenticated) {
                    Navigator.pushReplacementNamed(context, '/login');
                  }
                },
                little one: BlocBuilder<ReservationsFindAllBloc,
                    ReservationsFindAllState>(
                  builder: (_, state) {
                    if (state is ReservationsLoading) {
                      return const Middle(
                        little one: 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.knowledge!));
    }

    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.knowledge!));
    }

    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 person = await _secureStorage.getUser();

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

and that is the interceptor:

If the comply with assertion is true else if (response is DataFailed) then the person must 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('Information for request...');
    print(choices.knowledge);
    choices.headers['Accept'] = 'utility/json';
    if (choices.headers.containsKey('Content material-Kind')) {
      choices.headers['Content-Type'] = 'utility/json';
    }

    if (!choices.additional.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?.knowledge['error'] == 'Token expired.') {
      strive {
        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.additional['isRetry'] = true; // Set flag to point retry
            retryOptions.headers['Authorization'] =
                'Bearer ${response.knowledge!.accessToken}';
            // Resend the request with up to date choices
            last updatedResponse = await _dio.request(
              retryOptions.uri.toString(),
              choices: Choices(
                methodology: retryOptions.methodology,
                headers: retryOptions.headers,
                responseType: retryOptions.responseType,
              ),
              knowledge: retryOptions.knowledge,
              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 person 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 rather a lot!

Latest news
Related news

LEAVE A REPLY

Please enter your comment!
Please enter your name here