I am building a flutter web application and I'm using Bloc. I've created an 'authOrApp' route to check if the user is authenticated, but when I first run the web app it quickly loads the home screen twice when authenticated. Problem is that the home screen has another bloc with relatively heavy processing to do, and when it loads twice the loading process turns too slow. Any tips on why it's loading twice? I couldn't figure it out myself.
Here is the code - main
:
import 'dart:io';
import 'package:envelog/manage_users/bloc/manage_users_bloc.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:envelog/auth_or_app/bloc/auth_or_app_bloc.dart';
import 'package:envelog/values/bloc/values_bloc.dart';
import 'package:envelog/values/models/values.dart';
import 'package:envelog/excerpt/bloc/excerpt_bloc.dart';
import 'package:envelog/excerpt/models/excerpt.dart';
import 'package:envelog/expense/bloc/expense_bloc.dart';
import 'package:envelog/expense/models/expense.dart';
import 'package:envelog/login/bloc/login_bloc.dart';
import 'package:envelog/login/models/logged_user.dart';
import 'package:envelog/nfc/bloc/nfc_bloc.dart';
import 'package:envelog/nfc/models/nfc.dart';
import 'package:envelog/observation/bloc/observation_bloc.dart';
import 'package:envelog/observation/models/observation.dart';
import 'package:envelog/refuel/bloc/refuel_bloc.dart';
import 'package:envelog/refuel/models/refuel.dart';
import 'package:envelog/side_menu/bloc/side_menu_bloc.dart';
import 'package:envelog/trip/bloc/trip_bloc.dart';
import 'package:envelog/trip/models/trip.dart';
import 'package:envelog/utils/app_routes.dart';
// ignore: depend_on_referenced_packages
import 'package:flutter_localizations/flutter_localizations.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async {
await Hive.initFlutter();
await hiveAdapters();
// await Hive.deleteBoxFromDisk('pendingNfcOperations'); // substitua pelo nome correto da box
await Hive.openBox<LoggedUser>('loggedUser');
await Hive.openBox<Trip>('trips');
await Hive.openBox<Nfc>('nfcs');
await Hive.openBox<Values>('values');
await Hive.openBox<Refuel>('refuels');
await Hive.openBox<Observation>('observations');
await Hive.openBox<Excerpt>('excerpts');
await Hive.openBox<Expense>('expenses');
await Hive.openBox<Map>('pendingTripOperations');
await Hive.openBox<Map>('pendingValuesOperations');
await Hive.openBox<Map>('pendingNfcOperations');
await Hive.openBox<Map>('pendingExcerptOperations');
await Hive.openBox<Map>('pendingExpenseOperations');
await Hive.openBox<Map>('pendingRefuelOperations');
await Hive.openBox<Map>('pendingObservationOperations');
HttpOverrides.global = MyHttpOverrides();
runApp(const MyApp());
}
Future<void> hiveAdapters() async {
Hive.registerAdapter(LoggedUserAdapter());
Hive.registerAdapter(TripAdapter());
Hive.registerAdapter(ExcerptAdapter());
Hive.registerAdapter(ExpenseAdapter());
Hive.registerAdapter(NfcAdapter());
Hive.registerAdapter(RefuelAdapter());
Hive.registerAdapter(ObservationAdapter());
Hive.registerAdapter(ValuesAdapter());
}
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
final client = super.createHttpClient(context);
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return client;
}
}
http.Client createHttpClientWithBypass() {
final ioClient = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return IOClient(ioClient);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => LoginBloc(),
),
if (!kIsWeb)
BlocProvider(
create: (context) => AuthOrAppBloc(),
),
if (kIsWeb)
BlocProvider(
create: (context) => AuthOrAppBloc()..add(AuthOrAppInitialEvent()),
),
BlocProvider(
create: (context) => SideMenuBloc(),
),
BlocProvider(
create: (context) => TripBloc()..add(TripInitialEvent()),
),
BlocProvider(
create: (context) => ExcerptBloc(),
),
BlocProvider(
create: (context) => ExpenseBloc(),
),
BlocProvider(
create: (context) => NfcBloc(),
),
BlocProvider(
create: (context) => RefuelBloc(),
),
BlocProvider(
create: (context) => ObservationBloc(),
),
BlocProvider(
create: (context) => ValuesBloc(),
),
BlocProvider(
create: (context) => ManageUsersBloc(),
),
],
child: MaterialApp(
navigatorKey: navigatorKey,
localizationsDelegates: const [
...GlobalMaterialLocalizations.delegates,
GlobalWidgetsLocalizations.delegate,
// Other localization delegates if needed
],
supportedLocales: const [
Locale('pt', 'BR'), // Português (Brasil)
// Outros idiomas suportados
],
title: 'Portal Envelope de Viagem',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.yellow,
primary: const Color.fromRGBO(0, 53, 142, 1),
secondary: const Color.fromRGBO(121, 216, 83, 1)),
useMaterial3: false,
),
routes: AppRoutes.routes,
debugShowCheckedModeBanner: false,
),
);
}
}
App routes:
import 'package:envelog/auth_or_app/pages/auth_or_app_page.dart';
import 'package:envelog/login/ui/login_page.dart';
import 'package:envelog/manage_users/pages/manage_users_page.dart';
import 'package:envelog/trip/pages/trip_detail_page.dart';
import 'package:envelog/trip/pages/trip_page.dart';
import 'package:envelog/values/pages/values_page_2.dart';
class AppRoutes {
// static const String homeRoute = "/home";
static const String homeRoute = "/home";
static const String homeRouteWeb = "/home-web";
static const String tripDetailRoute = "/trip-detail";
static const String envelopeRoute = "/envelope";
static const String manageUsersRoute = "/manage-users";
static const String login = "/login";
static const String confirmResetPassword = '/confirm-reset-password';
static const String updateAppPage = "/update-app-page";
static const String authOrAppPage = "/";
static final routes = {
homeRoute: (context) => const TripPage(),
// homeRouteWeb: (context) => const TripPageWeb(),
tripDetailRoute: (context) => const TripDetailPage(),
envelopeRoute: (context) => const ValuesPage(),
manageUsersRoute: (context) => const ManageUsersPage(),
login: (context) => const LoginPage(),
// confirmResetPassword: (context) => const ConfirmResetPasswordPage(),
// updateAppPage: (context) => const UpdateAppPage(),
authOrAppPage: (context) => const AuthOrAppPage(),
};
}
Auth or app page:
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:envelog/auth_or_app/bloc/auth_or_app_bloc.dart';
import 'package:envelog/utils/app_routes.dart';
import 'package:upgrader/upgrader.dart';
class AuthOrAppPage extends StatefulWidget {
const AuthOrAppPage({super.key});
@override
State<AuthOrAppPage> createState() => _AuthOrAppPageState();
}
class _AuthOrAppPageState extends State<AuthOrAppPage> {
bool canNavigate = false;
Future<void> checkForUpgradeAndInit() async {
final authOrAppBloc = context.read<AuthOrAppBloc>();
if (kIsWeb) {
setState(() {
canNavigate = true;
});
authOrAppBloc.add(AuthOrAppInitialEvent());
} else {
final upgrader = Upgrader(
// debugLogging: true,
countryCode: 'BR',
languageCode: 'pt',
);
await upgrader.initialize();
final shouldUpdate = upgrader.shouldDisplayUpgrade();
if (!shouldUpdate) {
setState(() {
canNavigate = true;
});
authOrAppBloc.add(AuthOrAppInitialEvent());
} else {
// Aguarda o fechamento do UpgradeAlert
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) => UpgradeAlert(
upgrader: upgrader,
showIgnore: false,
showLater: false,
child: const SizedBox.shrink(),
),
);
// Recomeça fluxo BLoC após upgrade
setState(() {
canNavigate = true;
});
authOrAppBloc.add(AuthOrAppInitialEvent());
}
}
}
Widget body(state) {
switch (state.runtimeType) {
case const (AuthOrAppLoadingState):
return const Center(
child: CircularProgressIndicator(),
);
default:
return const SizedBox();
}
}
@override
void initState() {
checkForUpgradeAndInit();
super.initState();
}
@override
Widget build(BuildContext context) {
final authOrAppBloc = context.read<AuthOrAppBloc>();
return BlocConsumer(
bloc: authOrAppBloc,
listenWhen: (previous, current) => current is AuthOrAppActionState,
buildWhen: (previous, current) => current is! AuthOrAppActionState,
listener: (context, state) async {
if (state is AuthOrAppAppState) {
if (kIsWeb) {
Navigator.of(context).pushNamedAndRemoveUntil(
AppRoutes.envelopeRoute,
(route) => false,
);
} else if (canNavigate) {
Navigator.of(context).pushNamedAndRemoveUntil(
AppRoutes.homeRoute,
(route) => false,
);
}
}
if (state is AuthOrAppAuthState && canNavigate) {
Navigator.of(context).pushNamedAndRemoveUntil(
AppRoutes.login,
(route) => false,
);
}
if (state is AuthOrAppNeedUpdateState) {
Navigator.of(context).pushNamedAndRemoveUntil(
AppRoutes.updateAppPage,
(route) => false,
);
}
},
builder: (context, state) {
return Scaffold(
body: Container(
child: body(state),
),
);
},
);
}
}
Auth or app bloc:
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:envelog/login/models/logged_user.dart';
import 'package:envelog/utils/main_api.dart';
part 'auth_or_app_event.dart';
part 'auth_or_app_state.dart';
class AuthOrAppBloc extends Bloc<AuthOrAppEvent, AuthOrAppState> {
AuthOrAppBloc() : super(AuthOrAppInitial()) {
on<AuthOrAppInitialEvent>(authOrAppInitialEvent);
}
FutureOr<void> authOrAppInitialEvent(
AuthOrAppInitialEvent event, Emitter<AuthOrAppState> emit) async {
emit(AuthOrAppLoadingState());
var box = await Hive.openBox<LoggedUser>('loggedUser');
var loggedUser = box.get('loggedUser');
final token = loggedUser?.token;
if (token == null) {
// Se o token for nulo, emitir o estado de autenticação
emit(AuthOrAppAuthState());
return;
}
try {
// Verificar validade do token com timeout
final response =
await checkToken(token).timeout(const Duration(seconds: 10));
if (response == 200) {
emit(AuthOrAppAppState());
} else {
emit(AuthOrAppAuthState());
}
} catch (e) {
emit(AuthOrAppAuthState()); // Em caso de erro, navega para tela de login
}
}
// Função para verificar o token chamando a API
Future<int> checkToken(String token) async {
final response =
await MainApi.doRequest('/check', token, 'GET', null, null);
if (response != null) {
return response.statusCode;
} else {
return 500; // Retornar erro genérico se a resposta for nula
}
}
}
I finally managed to fix the problem: instead of defining in bloc listener where to navigate according to state in auth or app page, i called the widgets on blocBuilder and changed from a switch case with state.runtimeType to an if clause with each possible state, and called the initial event whenever authOrAppState changes