Вопрос или проблема
После входа в систему, когда происходит перенаправление на главную страницу, инициализируются все страницы из widgetOptions. На странице корзины я вызываю API для получения корзины, а на странице профиля – API для получения профиля.
Моя проблема в том, что каждый раз, когда я попадаю на главную страницу, отображается домашняя страница, и при этом загружается API страницы профиля и API страницы корзины, и страница постоянно перестраивается снова и снова.
Я поставил отладчик на главную страницу, и он вызывается 5-10 раз в секунду. Состояние CartSuccessState вызывается несколько раз, так как страница постоянно пересоздается.
class RootPage extends StatefulWidget {
const RootPage({super.key});
@override
State<RootPage> createState() => _RootPageState();
}
class _RootPageState extends State<RootPage>
with AutomaticKeepAliveClientMixin {
int _selectedIndex = 0;
bool isInternetAvailable = false;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final List<Widget> widgetOptions = <Widget>[
const HomePage(),
const CategoryPage(),
const CartPage(),
const ProfilePage(),
];
void _onItemTapped(int index) {
if (_selectedIndex != index) {
if (index == 2 || index == 3) {
if (HiveBoxData.getAccessToken() != "" &&
HiveBoxData.getCartID() != "") {
setState(() {
_selectedIndex = index;
});
} else {
showLoginBottomSheet(context);
}
} else {
setState(() {
_selectedIndex = index;
});
}
}
}
late HomeBloc homeBloc;
@override
bool get wantKeepAlive => true;
@override
void initState() {
BlocProvider.of<HomeBloc>(context).add(MainMenu());
if (HiveBoxData.getAccessToken() != "" && HiveBoxData.getCartID() == "") {
BlocProvider.of<CartBloc>(context)
.add(CreateCart(customerAccessToken: HiveBoxData.getAccessToken()));
} else if (HiveBoxData.getAccessToken() != "" &&
HiveBoxData.getCartID() != "") {
BlocProvider.of<CartBloc>(context)
.add(FetchCart(catID: HiveBoxData.getCartID()));
}
super.initState();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
forceMaterialTransparency: true,
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () async {
Navigator.pushNamed(
context,
Routes.searchPage,
);
},
),
IconButton(
icon: const Icon(Icons.favorite_border),
onPressed: () {},
),
],
title: Row(
children: <Widget>[
Image.asset(
'assets/logo/shopify.png',
height: 40,
width: 30,
),
const SizedBox(
width: 10,
),
Text(
appName,
style: Theme.of(context).textTheme.titleMedium,
),
],
),
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {
_scaffoldKey.currentState?.openDrawer();
},
),
),
drawer: const MyDrawer(),
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: const Icon(Icons.home),
label: AppLocalizations.of(context)!.home,
),
BottomNavigationBarItem(
icon: const Icon(Icons.category),
label: AppLocalizations.of(context)!.category,
),
BottomNavigationBarItem(
icon: Stack(
children: <Widget>[
const Icon(Icons.shopping_cart),
BlocConsumer<CartBloc, CartStates>(
listener: (context, state) {},
builder: (context, state) {
print(state);
if (state is CartSuccessState ||
state is CartUpdateSuccessState) {
return Positioned(
right: 0,
child: Container(
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(6),
),
constraints: const BoxConstraints(
minWidth: 12,
minHeight: 12,
),
child: Text(
"${BlocProvider.of<CartBloc>(context).nodes.length}",
style: const TextStyle(
color: Colors.white,
fontSize: 8,
),
textAlign: TextAlign.center,
),
),
);
} else {
return const SizedBox();
}
})
],
),
label: AppLocalizations.of(context)!.cart,
),
BottomNavigationBarItem(
icon: const Icon(Icons.person),
label: AppLocalizations.of(context)!.profile,
),
],
currentIndex: _selectedIndex,
selectedItemColor: primaryMaterialColor,
unselectedItemColor: Colors.grey,
showUnselectedLabels: true,
onTap: _onItemTapped,
type: BottomNavigationBarType.fixed,
),
body: BlocListener(
bloc: BlocProvider.of<HomeBloc>(context),
listener: (context, state) {
if (state is LogoutSuccessState) {
Navigator.pushNamedAndRemoveUntil(
context, Routes.logInPage, (Route<dynamic> route) => false);
}
},
child: IndexedStack(
index: _selectedIndex,
children: widgetOptions,
)),
);
}
}
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
late ProfileBloc profileBloc;
@override
void initState() {
profileBloc = BlocProvider.of<ProfileBloc>(context);
profileBloc.add(GetUserProfileDetails(
customerAccessToken: HiveBoxData.getAccessToken()));
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocConsumer(
bloc: BlocProvider.of<ProfileBloc>(context),
listener: (context, state) {},
builder: (context, state) {
if (state is ProfileSuccessState) {
print(state);
return Column(
children: [
DrawerHeader(
decoration: const BoxDecoration(
color: Colors.lightGreen,
),
child: Padding(
padding: const EdgeInsets.only(left: 10, top: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(state.name,
style: Theme.of(context).textTheme.titleLarge),
Text(state.email,
style: Theme.of(context).textTheme.titleMedium),
],
)),
),
],
);
} else if (state is ProfileLoadingState) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
return Container();
}
});
}
}
class CartPage extends StatefulWidget {
const CartPage({super.key});
@override
State<CartPage> createState() => _CartPageState();
}
class _CartPageState extends State<CartPage> {
late CartBloc cartBloc;
@override
void initState() {
cartBloc = BlocProvider.of<CartBloc>(context);
cartBloc.add(FetchCart(catID: HiveBoxData.getCartID()));
super.initState();
}
@override
void dispose() {
super.dispose();
cartBloc.nodes = [];
}
@override
Widget build(BuildContext context) {
return BlocConsumer(
bloc: BlocProvider.of<CartBloc>(context),
builder: (context, state) {
if (state is CartSuccessState) {
if (cartBloc.nodes.isNotEmpty) {
return body();
} else {
return Center(
child: Text(
"В корзине нет товаров",
style: Theme.of(context).textTheme.bodyMedium,
),
);
}
} else if (state is CartLoadingState) {
return const SingleChildScrollView(
child: VerticalProductShimmer(productCount: 10));
} else {
if (cartBloc.nodes.isNotEmpty) {
return body();
} else {
return const SingleChildScrollView(
child: VerticalProductShimmer(productCount: 10));
}
}
},
listener: (context, state) {},
);
}
Ответ или решение
Проблема постоянного перезагрузки состояния в приложении
Описание проблемы
В вашем приложении, при входе и перенаправлении на главную страницу (Root Page), происходит инициализация всех страниц через список widgetOptions
. Это приводит к тому, что при отображении главной страницы автоматически запускаются API для получения информации о пользоателей и корзине каждый раз, когда происходит перезагрузка страницы, что вызывает многоразовые вызовы состояния CartSuccessState
. Дебаггер показывает, что состояние CartSuccessState
вызывается 5-10 раз в секунду, что указывает на бесконечный цикл перезагрузки страниц.
Причины проблемы
- Неправильное управление состоянием: Вы вызываете API для получения профиля и корзины внутри метода
initState
класса_RootPageState
. Эти вызовы происходят каждый раз, когдаsetState()
обновляет виджет, что вызывает повторный билд и, соответственно, повторные вызовы API. - Использование
BlocProvider
без контроля состояния: Вы используетеBlocProvider
для инициализации API, но не проверяете текущее состояние блока (например, было ли уже загружено состояние для пользователя или корзины).
Рекомендации по решению
-
Измените логику вызова API:
- Вместо того чтобы вызывать API в
initState
, вы можете использоватьdidChangeDependencies
или контролировать изменение состояния, чтобы выполнить необходимый код только один раз:
@override void didChangeDependencies() { super.didChangeDependencies(); if (!profileBloc.state is ProfileLoaded) { profileBloc.add(GetUserProfileDetails(customerAccessToken: HiveBoxData.getAccessToken())); } if (!cartBloc.state is CartLoaded) { cartBloc.add(FetchCart(catID: HiveBoxData.getCartID())); } }
- Вместо того чтобы вызывать API в
-
Снизьте частоту вызовов состояния:
- Убедитесь, что вы не вызываете
setState()
без необходимости. Это прекратит бесконечное построение виджета и вызовы соответствующих блоков.
- Убедитесь, что вы не вызываете
-
Используйте
IndexedStack
:IndexedStack
уже сохраняет состояние, поэтому при переключении между вкладками состояние не теряется. Убедитесь, что вы используетеsetState()
только когда это действительно необходимо.
-
Проверьте состояние на уровне представления:
- Вы также можете добавить дополнительные проверки состояния в ваших виджетах
ProfilePage
иCartPage
:
@override Widget build(BuildContext context) { return BlocConsumer<ProfileBloc, ProfileState>( listener: (context, state) { // Реакция на изменение состояния }, builder: (context, state) { if (state is ProfileLoadingState) { return const CircularProgressIndicator(); } else if (state is ProfileSuccessState) { return ...; // Ваш виджет } return Container(); } ); }
- Вы также можете добавить дополнительные проверки состояния в ваших виджетах
Заключение
Следуя приведенным рекомендациям, вы сможете стабилизировать ваше приложение и предотвратить постоянные вызовы состояния, что в свою очередь улучшит производительность и пользовательский опыт. Важно всегда контролировать состояние приложения и избегать лишних вызовов API, особенно в условиях, когда состояния уже загружены.