我有一个小部件,可向返回地图的api发出请求。我想做的是每次加载窗口小部件并将列表保存到appState.myList
时都不会发出相同的请求,但是当我在appState.myList = snapshot.data;
中执行此FutureBuilder
时,出现以下错误:
flutter: ══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
flutter: The following assertion was thrown while dispatching notifications for MySchedule:
flutter: setState() or markNeedsBuild() called during build.
flutter: This ChangeNotifierProvider<MySchedule> widget cannot be marked as needing to build because the
flutter: framework is already in the process of building widgets. A widget can be marked as needing to be
flutter: built during the build phase only if one of its ancestors is currently building. ...
sun.dart文件
class Sun extends StatelessWidget {
Widget build(BuildContext context) {
final appState = Provider.of<MySchedule>(context);
var db = PostDB();
Widget listBuild(appState) {
final list = appState.myList;
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return ListTile(title: Text(list[index].title));
},
);
}
Widget futureBuild(appState) {
return FutureBuilder(
future: db.getPosts(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
// appState.myList = snapshot.data;
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data[index].title));
},
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Center(
child: CircularProgressIndicator(),
);
},
);
}
return Scaffold(
body: appState.myList != null
? listBuild(appState)
: futureBuild(appState));
}
}
postService.dart文件
class PostDB {
var isLoading = false;
Future<List<Postmodel>> getPosts() async {
isLoading = true;
final response =
await http.get("https://jsonplaceholder.typicode.com/posts");
if (response.statusCode == 200) {
isLoading = false;
return (json.decode(response.body) as List)
.map((data) => Postmodel.fromJson(data))
.toList();
} else {
throw Exception('Failed to load posts');
}
}
}
我知道myList
会调用notifyListeners()
,这就是导致错误的原因。希望我说对了。如果是这样,如何设置appState.myList
并在应用中使用而不会出现上述错误?
import 'package:flutter/foundation.dart';
import 'package:myflutter/models/post-model.dart';
class MySchedule with ChangeNotifier {
List<Postmodel> _myList;
List<Postmodel> get myList => _myList;
set myList(List<Postmodel> newValue) {
_myList = newValue;
notifyListeners();
}
}
答案 0 :(得分:0)
使用提供程序时,我遇到了与您类似的问题。我的解决方案是在获取数据时添加WidgetsBinding.instance.addPostFrameCallback()
。
答案 1 :(得分:0)
只需从代码中删除notifyListeners();
。我遇到此错误,这就是我要解决的问题。
答案 2 :(得分:-1)
如果您不依赖使用MySchedule
(从您提供的内容中看不到原因),则可以简单地使用async
软件包中的AsyncMemoizer
(根据标准库),它将仅在将来运行一次,而不是在每次小部件重建时运行。
在StatelessWidget
中,您可以将备忘录存储为final
变量,然后使用runOnce
。
import 'package:async/async.dart';
...
final AsyncMemoizer<List<Postmodel>> memoizer = AsyncMemoizer();
当您无法调用build
时,您也会摆脱条件setState
的功能(当您在正在监听的由notifyListeners
函数访问的)。无论如何,它也不是必需的,而且很可能是不良的设计。此外,正如Rousselet所指出的,build
会丢失状态,因此无论如何在再次创建StatelessWidget
时仍要重新加载数据。我最初只是想分享memoizer
的基本要点,但是,没有理由对此进行调整。因此,在这种情况下,您应该使用AsyncMemoizer
。
StatefulWidget
当您想在多个窗口小部件中使用列表时,建议您在class _SunState extends State<Sun> {
final AsyncMemoizer<List<Postmodel>> memoizer;
final PostDB db;
@override
void initState() {
memoizer = AsyncMemoizer();
db = PostDB();
super.initState();
}
Future<List<Postmodel>> getPosts() => db.getPosts();
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: memoizer.runOnce(getPosts),
builder: (BuildContext context, AsyncSnapshot<List<Postmodel>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(snapshot.data[index].title));
});
}
if (snapshot.hasError) return Text('${snapshot.error}');
return Center(child: const CircularProgressIndicator());
},
));
}
}
类中使用Completer
,并在提供程序中存储该类的实例。
您可能希望调整以下代码的确切实现,但是,这应该使您了解如何实现所需的代码。
PostDB
class PostDB {
Completer<List<Postmodel>> _completer;
/// Return `null` if [getPosts] has not yet been called.
bool get isLoading => _completer == null ? null : !_completer.isCompleted;
/// This will allow you to call [getPosts] multiple times
/// by supplying [reload], which will reassign [_completer].
Future<List<Postmodel>> getPosts([bool reload = false]) {
if (_completer == null || reload) {
_completer = Completer();
_completer.complete(_getPosts());
}
return _completer.future;
}
Future<List<Postmodel>> _getPosts() async {
final response = await http.get("https://jsonplaceholder.typicode.com/posts");
if (response.statusCode == 200) {
return (json.decode(response.body) as List).map((data) => Postmodel.fromJson(data)).toList();
} else {
throw Exception('Failed to load posts');
}
}
}
class MySchedule {
PostDB db;
}
class Sun extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appState = Provider.of<MySchedule>(context);
return Scaffold(
body: FutureBuilder(
future: appState.db.getPosts(),
builder: (BuildContext context, AsyncSnapshot<List<Postmodel>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(snapshot.data[index].title));
});
}
if (snapshot.hasError) return Text('${snapshot.error}');
return Center(child: const CircularProgressIndicator());
},
));
}
}
允许您返回Completer
,即使请求已经完成,也可以使函数调用与第一次和后续调用相同(只是数据仅加载一次)。
已对该代码进行了调整,以更好地满足您的需求,但是,看起来好像我永远不会在自己的应用程序中包含这些内容。您现在可能已经有足够的信息,并且 起作用了,这就是为什么这应该足够的原因。任何其他更改都必须由您自己完成,因为只有您知道您的应用程序。
答案 3 :(得分:-1)
出现该异常是因为您正在从其后代同步修改窗口小部件。
这很糟糕,因为它可能导致小部件树不一致。一些小部件。可能会使用突变前的值构建小部件,而其他人可能正在使用已突变的值。
解决方案是消除不一致之处。使用ChangeNotifierProvider
,通常有两种情况:
ChangeNotifier
上执行的变异总是在与创建ChangeNotifier
的变异相同的 build 中完成。在这种情况下,您可以直接从ChangeNotifier
的构造函数进行调用:
class MyNotifier with ChangeNotifier {
MyNotifier() {
// TODO: start some request
}
}
在这种情况下,您应该将突变包装在addPostFrameCallback
或Future.microtask
中:
class Example extends StatefulWidget {
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
MyNotifier notifier;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final notifier = Provider.of<MyNotifier>(context);
if (this.notifier != notifier) {
this.notifier = notifier;
Future.microtask(() => notifier.doSomeHttpCall());
}
}
@override
Widget build(BuildContext context) {
return Container();
}
}