#flutter
#flutter
Вопрос:
Я перешел от Statefulwidget
использования initState
для извлечения данных и Futurebuilder
их загрузки Futureprovider
. Но похоже Futureprovider
build
, что метод выполняется дважды, в то время как мой предыдущий подход выполнял его один раз. Это нормальное поведение?
class ReportsPage extends StatelessWidget {
const ReportsPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return FutureProvider<List<ReportModel>>(
create: (_) async => ReportsProvider().loadReportData(1),
initialData: null,
catchError: (_, __) => null,
child: const ReportWidg()
);
}
}
class ReportWidg extends StatelessWidget {
const ReportWidg();
@override
Widget build(BuildContext context) {
print("Execute Build");
final reportList = Provider.of<List<ReportModel>>(context);
if (reportList == null) {
return Center(child: CircularProgressIndicator());
} else if (reportList.isEmpty) {
return Center(child: Text("Det finns inga rapporter."));
}
print(reportList.length);
return Container();
}
}
Ответ №1:
Я относительно новичок в flutter, но я думаю, что это потому StatelessWidget
@immutable
, что это означает, что всякий раз, когда что-то меняется, ему нужно перестраиваться.
При первой сборке выполняется async
вызов и ReportWidg()
выполняется рендеринг.
Затем эта строка final reportList = Provider.of<List<ReportModel>>(context);
получает новые извлеченные данные в результате async
функции, поэтому immutable
виджету необходимо перестроить себя, потому что его нельзя «изменить».
В объектно-ориентированном и функциональном программировании неизменяемый объект (неизменяемый объект) — это объект, состояние которого нельзя изменить после его создания. … Это в отличие от изменяемого объекта (изменяемый объект), который может быть изменен после его создания
или я ошибаюсь?
Ответ №2:
Я подозреваю, что ваш FutureProvider следует перенести в один экземпляр, например, поместить в глобальную переменную вне любых методов build () . Это, конечно, приведет к кэшированию результата, поэтому вы можете настроить его для перестроения, установив значение в зависимости от того, какие другие поставщики используют watch() или через FutureProvider.family .
Ответ №3:
Вы можете скопировать вставить запустить полный код ниже
Да. это нормально
, что в первый раз Execute Build
reportList
null
и показывает CircularProgressIndicator()
Во второй раз Execute Build
reportList
есть данные и показать данные
Если вы установили listen: false
, final reportList = Provider.of<List<ReportModel>>(context, listen: false);
Вы получаете только один Execute Build
, и экран всегда будет отображаться CircularProgressIndicator()
В рабочей демонстрации имитируйте 5-секундную задержку в сети, чтобы вы могли видеть CircularProgressIndicator()
, а затем показывать ListView
Вы можете ссылаться https://codetober.com/flutter-provider-examples /
фрагмент кода
Widget build(BuildContext context) {
print("Execute Build");
final reportList = Provider.of<List<ReportModel>>(context);
print("reportList ${reportList.toString()}");
if (reportList == null) {
print("reportList is null");
return Center(child: CircularProgressIndicator());
} else if (reportList.isEmpty) {
return Center(child: Text("Empty"));
}
return Scaffold(
body: ListView.builder(
itemCount: reportList.length,
рабочая демонстрация
полный код
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ReportModel {
String title;
ReportModel({this.title});
}
class ReportsProvider with ChangeNotifier {
Future<List<ReportModel>> loadReportData(int no) async {
await Future.delayed(Duration(seconds: 5), () {});
return Future.value([
ReportModel(title: "1"),
ReportModel(title: "2"),
ReportModel(title: "3")
]);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ReportsPage(),
);
}
}
class ReportsPage extends StatelessWidget {
const ReportsPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return FutureProvider<List<ReportModel>>(
create: (_) async => ReportsProvider().loadReportData(1),
initialData: null,
catchError: (_, __) => null,
child: const ReportWidg());
}
}
class ReportWidg extends StatelessWidget {
const ReportWidg();
@override
Widget build(BuildContext context) {
print("Execute Build");
final reportList = Provider.of<List<ReportModel>>(context);
print("reportList ${reportList.toString()}");
if (reportList == null) {
print("reportList is null");
return Center(child: CircularProgressIndicator());
} else if (reportList.isEmpty) {
return Center(child: Text("Empty"));
}
return Scaffold(
body: ListView.builder(
itemCount: reportList.length,
itemBuilder: (context, index) {
return Card(
elevation: 6.0,
child: Padding(
padding: const EdgeInsets.only(
top: 6.0, bottom: 6.0, left: 8.0, right: 8.0),
child: Text(reportList[index].title.toString()),
));
}),
);
}
}
Ответ №4:
В вашем случае вы должны использовать Consumer
, т.е.
FutureProvider<List<ReportModel>(
create: (_) => ...,
child: Consumer<List<ReportModel>(
builder: (_, reportList, __) {
return reportList == null ?
CircularProgressIndicator() :
ReportWidg(reportList);
}
),
),
Но в этом случае вам необходимо провести рефакторинг вашего ReportWidg
.
Комментарии:
1. Во втором подходе не правда ли, что он не получит новые асинхронные данные? или его трудно определить, потому что он асинхронный…
2. После некоторого размышления я пришел к выводу, что автор второго ответа прав, поэтому подход, который
listen: false
не является хорошей идеей. So .. onlyConsumer
— это обычный способ. При аренде я всегда использую это. 😉