I am trying to navigate to QuestCartScreen after tapping on discover more quests button.I am using bloc for state management. But it is taking long time to navigate. The action occurs from homepage from the widget _buildAddMoreQuestsContainer.But navigation triggered from home screen.Homepage is the child of homescreen. Navigation occurs after state is QuestsDataLoaded.Is it because of naviagting after data is loaded. My navigation screen requires parameters from the state chanages.
I have given codes of onboarding bloc , Homescreen and homepage.
class HomeScreen extends StatefulWidget {
static const String routeName = '/home';
final AuthUtilities authUtilities;
const HomeScreen({super.key, required this.authUtilities});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool _onboardingHasError = false;
@override
void dispose() {
super.dispose();
}
@override
void initState() {
super.initState();
_resetErrorState();
context.read<HomeBloc>().add(FetchDataEvent());
}
// Centralized method to set error state
void _setErrorState(bool hasError) {
if (_onboardingHasError != hasError) {
setState(() {
_onboardingHasError = hasError;
});
}
}
// Centralized method to reset error state
void _resetErrorState() {
_setErrorState(false);
}
Future<void> _refreshData() async {
context.read<HomeBloc>().add(FetchDataEvent());
}
@override
Widget build(BuildContext context) {
return BlocConsumer<HomeBloc, HomeState>(
listener: (context, homeState) {
// Handle state changes in the listener instead of during build
if (homeState is! OnboardingIncomplete) {
_resetErrorState();
}
},
builder: (context, homeState) {
return BlocListener<OnboardingBloc, OnboardingState>(
listener: (context, onboardingState) {
if (onboardingState is OnboardingError) {
_setErrorState(true); // Set error state immediately
// Show a snackbar for network errors
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(onboardingState.message),
duration: const Duration(seconds: 3),
),
);
} else if (onboardingState is QuestSelectionCompleted ||
onboardingState is OnboardingCompleted) {
_resetErrorState();
}
},
child: Builder(builder: (context) {
// Simplified logic: only hide AppBar during normal onboarding process
// Always show AppBar during errors
final bool hideAppBar =
homeState is OnboardingIncomplete && !_onboardingHasError;
return AuthenticatedScaffold(
forceHideAppBar: hideAppBar,
isLogoClickable: false,
currentPage: CurrentRouteEnum.home,
currentNavIndex: 0,
hideBottomNavBar: hideAppBar ||
_onboardingHasError, // Hide bottom nav during onboarding
body: MultiBlocListener(
listeners: [
BlocListener<AuthBloc, AuthState>(listener: (context, state) {
if (state is AuthUnauthenticated) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => const LoginScreen()),
(Route<dynamic> route) => false,
);
}
}),
BlocListener<OnboardingBloc, OnboardingState>(
listener: (context, state) {
if (state is QuestsDataLoaded) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuestCartScreen(
quests: state.quests,
userData: state.userData,
onBack: () {},
isOnboardingProcess: false,
)));
}
})
],
child: Stack(children: [
BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) {
if (state is HomeLoading || state is HomeInitial) {
return const AppLoaderWidget();
} else if (state is OnboardingIncomplete) {
return OnboardingFlow(
userData: state.userData,
onErrorStateChanged: _setErrorState,
);
} else if (state is OnboardingComplete) {
return RefreshIndicator(
onRefresh: _refreshData,
child: ListView(
children: [
HomePage(
userData: state.userData,
questData: state.questData,
),
],
),
);
}
]),
),
);
}),
);
},
);
}
class HomePage extends StatelessWidget {
final User userData;
final List<Quest> questData;
const HomePage({
super.key,
required this.userData,
required this.questData,
});
@override
Widget build(BuildContext context) {
final chartData = ChartUtils.transformUserJourneyData(
userData.points, AppColors.doughtnutFillColor);
final List<Quest> incompleteQuests =
QuestUtils.getIncompleteAndActiveQuests(questData: questData);
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildMainContainer(context, chartData, incompleteQuests),
],
),
);
}
Widget _buildMainContainer(BuildContext context,
List<RadialComponent> chartData, List<Quest> incompleteQuests) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 10.h), // adapt height to screen size
_buildSectionTitle(
context, 'Welcome back, ${userData.name.split(' ')[0]}!'),
SizedBox(height: 5.h),
GraphPresentation(
height: 230.h,
width: double.infinity,
tooltipBehavior: TooltipBehavior(),
chartData: chartData,
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const PortfolioScreen();
}));
},
),
SizedBox(height: 5.h),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.h),
child: Text(
AppLiterals.quests,
style: AppTextStyles.subtitle,
),
),
_buildQuestsGrid(context, incompleteQuests),
SizedBox(height: 5.h),
],
),
);
}
Widget _buildQuestsGrid(
BuildContext context,
List<Quest> incompleteQuests,
) {
return Container(
margin: EdgeInsets.all(5.w),
decoration: BoxDecoration(
color: AppColors.lightGreyVariant,
borderRadius: BorderRadius.circular(15),
),
child: GridBuilderWidget(
itemCount:
incompleteQuests.length + (incompleteQuests.length < 4 ? 1 : 0),
itemBuilder: (p1, index) {
if (index < incompleteQuests.length) {
return _buildQuestCartContainer(context, incompleteQuests[index]);
}
return _buildAddMoreQuestsContainer(context);
},
));
Widget _buildAddMoreQuestsContainer(
BuildContext context,
) {
return BlocBuilder<OnboardingBloc, OnboardingState>(
builder: (context, state) {
return Semantics(
label: AppLiterals.discoverQuests,
child: CustomContainerButtonWidget(
imageWidget: Padding(
padding: EdgeInsets.only(top: 10.h), // Add space on top
child: Image.asset(
AppImages.imagAddIcon,
height: 40.h,
width: 40.h,
),
),
showPrimaryButton: true,
title: AppLiterals.discoverQuests,
color: Colors.white,
isLoading: state is OnboardingLoading,
onPressed: () {
// Safely dispatch the event through a future to avoid build-phase errors
Future.microtask(() {
if (context.mounted) {
context.read<OnboardingBloc>().add(FetchQuestCartsData());
}
});
},
),
);
});
}
}
class OnboardingBloc
extends flutter_bloc.Bloc<OnboardingEvent, OnboardingState> {
final QuestService questService;
final CauseService causeService;
final SkillService skillService;
final UserService userService;
final NGOService ngoService;
final AuthBloc authBloc;
User? _cachedUserData;
OnboardingBloc({
required this.questService,
required this.causeService,
required this.skillService,
required this.userService,
required this.ngoService,
required this.authBloc,
}) : super(OnboardingInitial()) {
_initializeUserData();
on<FetchQuestCartsData>(_handleFetchingQuestCartsData);
on<PostQuestCarts>(_handlePostQuestId);
}
Future<void> _handleFetchingQuestCartsData(FetchQuestCartsData event,
flutter_bloc.Emitter<OnboardingState> emit) async {
emit(OnboardingLoading());
try {
final List<Quest> quests =
await questService.fetchRecommendedQuestsData();
final userData = await _getUserData();
emit(QuestsDataLoaded(
quests: quests,
userData: userData,
));
} catch (e) {
emit(OnboardingError(message: 'Failed to fetch the quest data:--$e'));
}
}
}
After analyzing the code I can see that the navigation happens AFTER awaiting for the questService.fetchRecommendedQuestsData() and _getUserData() are done, so the issue is not bloc related, what you can do is navigate to the other screen directly then load the data, you can display a loading indicator meanwhile the data is loading
So the flow will be
- User tap -> event triggered + navigating to the new screen
- In the new screen have a Bloc builder and there you can show the loading indicator or the data as soon as it arrives