I have a widget that makes a request to an api which returns a map. What I would like to do is not make the same request every time the widget is loaded and save the list to appState.myList
. But. when I do this appState.myList = snapshot.data;
in the FutureBuilder
, I get the following error:
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
file:
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
file:
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');
}
}
}
I understand that the myList
calls notifyListeners()
and that's what causes the error. Hope I got that right. If so, how do I set appState.myList
and use in the app without getting the above error?
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();
}
}
That exception arises because you are synchronously modifying a widget from its descendants.
This is bad, as it could lead to an inconsistent widget tree. Some widget. may be built widgets using the value before mutation, while others may be using the mutated value.
The solution is to remove the inconsistency. Using
ChangeNotifierProvider
, there are usually 2 scenarios:The mutation performed on your
ChangeNotifier
is always done within the same build than the one that created yourChangeNotifier
.In that case, you can just do the call directly from the constructor of your
ChangeNotifier
:The change performed can happen "lazily" (typically after changing page).
In that case, you should wrap your mutation in an
addPostFrameCallback
or aFuture.microtask
:I have encountered a problem similar to yours when using the provider. My solution is to add
WidgetsBinding.instance.addPostFrameCallback()
when getting the data.