Вопрос или проблема
У меня есть состояние виджет, и я сталкиваюсь с странной проблемой: каждый раз, когда я нажимаю на любой элемент, такой как CustomDropDownButtonFormField, и открываю выпадающее меню, даже не выбрав ни один вариант в выпадающем меню, вызывается метод build(BuildContext context) и экран перезагружается. Это странно. Я также попробовал закомментировать setState везде в этом виджете, но проблема все еще существует. Я потратил много времени на это. Но я не могу решить эту проблему. Пожалуйста, посмотрите на мой код и дайте мне знать, что с ним не так.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:np_casse/app/customized_component/sliver_grid_delegate_fixed_cross_axis_count_and_fixed_height.dart';
import 'package:np_casse/componenents/custom.drop.down.button.form.field.field.dart';
import 'package:np_casse/core/models/category.catalog.model.dart';
import 'package:np_casse/core/models/product.catalog.model.dart';
import 'package:np_casse/core/models/user.app.institution.model.dart';
import 'package:np_casse/core/notifiers/authentication.notifier.dart';
import 'package:np_casse/core/notifiers/product.catalog.notifier.dart';
import 'package:np_casse/screens/shopScreen/widget/product.card.dart';
import 'package:provider/provider.dart';
class ProductThreeShopScreen extends StatefulWidget {
const ProductThreeShopScreen({
Key? key,
required this.childCategoryCatalogModel,
}) : super(key: key);
final CategoryCatalogModel childCategoryCatalogModel;
State<ProductThreeShopScreen> createState() =>
__ProductThreeShopScreenState();
}
class __ProductThreeShopScreenState extends State<ProductThreeShopScreen> {
double widgetWitdh = 320;
double widgetHeight = 620;
double widgetHeightHalf = 470;
double gridMainAxisSpacing = 10;
Timer? _timer;
Icon icona = const Icon(Icons.search);
TextEditingController nameDescSearchController = TextEditingController();
bool viewOutOfAssortment = false;
bool readImageData = true;
bool readAlsoDeleted = false;
int selectedCategory = 0;
List<DropdownMenuItem<String>> availableCategory = [];
String numberResult="10";
List<DropdownMenuItem<String>> availableNumberResult = [
DropdownMenuItem(child: Text("Tutti"), value: "All"),
DropdownMenuItem(child: Text("10"), value: "10"),
DropdownMenuItem(child: Text("25"), value: "25"),
DropdownMenuItem(child: Text("50"), value: "50"),
];
String orderBy = 'NameProduct';
List<DropdownMenuItem<String>> availableOrderBy = [
DropdownMenuItem(child: Text("Nome"), value: "NameProduct"),
DropdownMenuItem(child: Text("Descrizione"), value: "DescriptionProduct"),
DropdownMenuItem(
child: Text("Ordine di visualizzazione"), value: "DisplayOrder"),
];
Icon iconaNameDescSearch = const Icon(Icons.search);
void onChangeCategory(value) {
setState(() {
selectedCategory = value!;
});
}
void onChangeNumberResult(value) {
setState(() {
numberResult = value!;
});
}
void onChangeOrderBy(value) {
setState(() {
orderBy = value!;
});
}
@override
void initState() {
nameDescSearchController.text = "";
viewOutOfAssortment = false;
super.initState();
}
@override
Widget build(BuildContext context) {
AuthenticationNotifier authenticationNotifier =
Provider.of<AuthenticationNotifier>(context);
// ProjectNotifier projectNotifier = Provider.of<ProjectNotifier>(context);
// StoreNotifier storeNotifier = Provider.of<StoreNotifier>(context);
UserAppInstitutionModel cUserAppInstitutionModel =
authenticationNotifier.getSelectedUserAppInstitution();
// bool canAddProduct = authenticationNotifier.canUserAddItem();
return SafeArea(
child: Scaffold(
backgroundColor: Theme.of(context).colorScheme.background,
// drawer: const CustomDrawerWidget(),
appBar: AppBar(
centerTitle: true,
title: Text(widget.childCategoryCatalogModel.nameCategory,
style: Theme.of(context).textTheme.headlineLarge),
),
body: SingleChildScrollView(
child: Column(
children: [
Row(
children: [
Expanded(
flex: 1,
child: CustomDropDownButtonFormField(
enabled: true,
actualValue: numberResult,
labelText: 'Mostra numero risultati',
listOfValue: availableNumberResult,
onItemChanged: (value) {
onChangeNumberResult(value);
},
)),
Expanded(
flex: 2,
child: CustomDropDownButtonFormField(
enabled: true,
actualValue: orderBy,
labelText: 'Ordinamento',
listOfValue: availableOrderBy,
onItemChanged: (value) {
onChangeOrderBy(value);
},
)),
Expanded(
flex: 1,
child: CheckboxListTile(
side: const BorderSide(color: Colors.blueGrey),
checkColor: Colors.blueAccent,
checkboxShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
activeColor: Colors.blueAccent,
controlAffinity: ListTileControlAffinity.leading,
value: viewOutOfAssortment,
onChanged: (bool? value) {
setState(() {
viewOutOfAssortment = value!;
});
},
title: Text(
'Visualizza fuori assortimento',
style: Theme.of(context)
.textTheme
.labelMedium!
.copyWith(color: Colors.blueGrey),
),
// subtitle: const Text(""),
),
),
Expanded(
flex: 1,
child: CheckboxListTile(
side: const BorderSide(color: Colors.blueGrey),
checkColor: Colors.blueAccent,
checkboxShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
activeColor: Colors.blueAccent,
controlAffinity: ListTileControlAffinity.leading,
value: readImageData,
onChanged: (bool? value) {
setState(() {
readImageData = value!;
});
},
title: Text(
'Visualizza immagine',
style: Theme.of(context)
.textTheme
.labelMedium!
.copyWith(color: Colors.blueGrey),
),
// subtitle: const Text(""),
),
),
Expanded(
flex: 2,
child: TextFormField(
style: Theme.of(context)
.textTheme
.labelLarge!
.copyWith(color: Colors.blueGrey),
onChanged: (String value) {
if (_timer?.isActive ?? false) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 1000), () {
setState(() {
iconaNameDescSearch = const Icon(Icons.cancel);
if (value.isEmpty) {
iconaNameDescSearch = const Icon(Icons.search);
}
});
});
},
controller: nameDescSearchController,
decoration: InputDecoration(
labelText: "Ricerca per nome, descrizione o barcode",
labelStyle: Theme.of(context)
.textTheme
.labelMedium!
.copyWith(color: Colors.blueGrey),
hintText: "Ricerca per nome, descrizione o barcode",
hintStyle: Theme.of(context)
.textTheme
.labelLarge!
.copyWith(
color:
Theme.of(context).hintColor.withOpacity(0.3)),
suffixIcon: IconButton(
icon: iconaNameDescSearch,
onPressed: () {
setState(() {
if (iconaNameDescSearch.icon == Icons.search) {
iconaNameDescSearch = const Icon(Icons.cancel);
} else {
iconaNameDescSearch = const Icon(Icons.search);
nameDescSearchController.text = "";
}
});
},
),
floatingLabelBehavior: FloatingLabelBehavior.always,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Colors.grey, width: 1.0),
),
enabledBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Colors.grey, width: 1.0),
),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Colors.blue, width: 1.0),
),
errorBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Colors.red, width: 1.0),
),
focusedErrorBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(
color: Colors.deepOrangeAccent, width: 1.0),
),
),
),
),
],
),
SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Consumer<ProductCatalogNotifier>(
builder: (context, productCatalogNotifier, _) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
width: MediaQuery.of(context).size.width,
child: FutureBuilder(
future: productCatalogNotifier.getProducts(
context: context,
token: authenticationNotifier.token,
idUserAppInstitution:
cUserAppInstitutionModel.idUserAppInstitution,
idCategory:
widget.childCategoryCatalogModel.idCategory,
readAlsoDeleted: false,
numberResult: numberResult,
nameDescSearch: nameDescSearchController.text,
orderBy: orderBy,
readImageData: readImageData,
shoWVariant: true,
viewOutOfAssortment: viewOutOfAssortment),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: SizedBox(
width: 100,
height: 100,
child: CircularProgressIndicator(
strokeWidth: 5,
color: Colors.redAccent,
))),
],
),
);
} else if (!snapshot.hasData) {
return const Center(
child: Text(
'No data...',
style: TextStyle(
color: Colors.redAccent,
),
),
);
} else {
var tSnapshot =
snapshot.data as List<ProductCatalogModel>;
var t = tSnapshot
.any((element) => element.imageData.isNotEmpty);
bool areAllWithNoImage = !t;
double cHeight = 0;
if (areAllWithNoImage) {
cHeight = widgetHeightHalf;
} else {
cHeight = widgetHeight;
}
return GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight(
crossAxisCount:
(MediaQuery.of(context).size.width) ~/
widgetWitdh,
crossAxisSpacing: 10,
mainAxisSpacing: gridMainAxisSpacing,
height: cHeight,
),
physics: const ScrollPhysics(),
shrinkWrap: true,
itemCount: tSnapshot.length,
// scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
ProductCatalogModel productCatalog =
tSnapshot[index];
return ProductCard(
productCatalog: productCatalog,
areAllWithNoImage: areAllWithNoImage);
});
}
},
),
);
},
),
),
],
),
),
));
}
}
Ответ или решение
Проблема, с которой вы сталкиваетесь в вашем приложении Flutter, заключается в том, что при нажатии на элемент в выпадающем списке происходит перерисовка вашего виджета, главная функция build(BuildContext context)
вызывается повторно, даже если вы не изменили состояние родительского виджета или не вызвали setState()
. Давайте рассмотрим возможные причины этой проблемы и предложим решения.
Возможные причины проблемы
-
Изменения состояния в родительском виджете: Если родительский виджет вашего
ProductThreeShopScreen
(например,Provider
) имеет состояние, которое изменяется, это может вызывать повторный вызовbuild()
для всех дочерних виджетов. Убедитесь, что родительский виджет не обновляет состояние или не вызываетsetState()
на каждом взаимодействии с дочерними виджетами. -
Использование
Provider
: При использованииProvider
важно убедиться, что только необходимые части дерева виджетов перерисовываются. Если вашProductCatalogNotifier
обновляет состояние, когда вы выбираете элемент в выпадающем списке, это также может привести к перерисовке. -
Функция коллбэка: Методы, такие как
onItemChanged
в ваших выпадающих списках, вызываютsetState()
, что может также привести к перерисовкам. Если изменения в состоянии не влияют на UI, рассмотрите возможность пересмотра подхода.
Решения
-
Изолируйте состояние: Попробуйте переместить управление состоянием из вашего
ProductThreeShopScreen
в отдельный виджет. Это позволит вам лучше контролировать, какие части UI должны перерисовываться при изменении состояния. -
Используйте
Consumer
илиSelector
из пакетаprovider
: Это позволяет вашему Widget получать обновления только тогда, когда состояние, к которому он подписан, изменяется. Например, вместо использованияProvider.of<AuthenticationNotifier>(context)
используйтеConsumer<AuthenticationNotifier>
. -
Проверьте логику обновления в ваших коллбэках:
- Убедитесь, что вы вызываете
setState()
только в случае, если данные действительно изменились. Например:void onChangeNumberResult(value) { if (numberResult != value) { setState(() { numberResult = value!; }); } }
- Убедитесь, что вы вызываете
-
Рассмотрите альтернативные подходы к выпадающим спискам: Попробуйте использовать виджет
DropdownButton
вместо отдельного выпадающего виджета, который может непредсказуемо вызыватьsetState()
. -
Оптимизация FutureBuilder:
- Убедитесь, что
FutureBuilder
обновляется только при необходимости. Например, вместо того чтобы вызыватьgetProducts
каждый раз, когда происходит обновление состояния, вы можете вызывать его только при изменении необходимых параметров.
- Убедитесь, что
-
Методы управления состоянием: Если ваша логика становится слишком сложной для управления с помощью состояния виджетов, подумайте о других подходах управления состоянием, таких как BLoC, Redux или Riverpod, которые могут помочь лучше изолировать состояние и его изменения, минимизируя повторные рендеры.
Заключение
При разработке приложений на Flutter, управление состоянием и избегание ненужных повторных рендеров — ключевые аспекты, которые могут повлиять на производительность и взаимодействие пользователя. Применяя вышеупомянутые практики, вы сможете уменьшить вероятность перерисовки вашего виджета и повысить стабильность вашего приложения. Если у вас возникнут дополнительные вопросы, оставайтесь на связи, и мы будем рады помочь вам в решении любых затруднений.