Reputation: 3181
I am using API call to get data and display in a ListView.
Here is the future builder class:
Widget futureBuilder() {
return FutureBuilder<List<Project>>(
future: _getProjects(),
builder: (context, snapshot) {
debugPrint('Builder');
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return listWidget(snapshot);
}
},
);
}
}
The issue is, debugPrint('Builder') is called even before _getProjects() returns result, so snapshot is passed as null in my ListView Widget.
Here is _getProjects() class:
Future<List<Project>> _getProjects() async {
List<Project> projects = [];
String getProjects = "https://api.myjson.com/bins/1g3xpe";
var response = await http.get(getProjects);
Iterable list = json.decode(response.body);
projects = list.map((model) => Project.fromJson(model)).toList();
debugPrint('Size ' + projects.length.toString());
return projects;
}
Here is the full code:
class Projects extends StatefulWidget {
@override
ProjectState createState() => new ProjectState();
}
class ProjectState extends State {
Future<List<Project>> _getProjects() async {
List<Project> projects = [];
String getProjects = "https://api.myjson.com/bins/1g3xpe";
var response = await http.get(getProjects);
Iterable list = json.decode(response.body);
projects = list.map((model) => Project.fromJson(model)).toList();
debugPrint('Size ' + projects.length.toString());
return projects;
}
@override
Widget build(BuildContext context) {
return Scaffold(
primary: true,
appBar: EmptyAppBar(),
body: Column(
children: <Widget>[
headerWidget(),
futureBuilder()
],
),
);
}
Widget futureBuilder() {
return FutureBuilder<List<Project>>(
future: _getProjects(),
builder: (context, snapshot) {
debugPrint('Builder');
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return listWidget(snapshot);
}
},
);
}
}
Widget headerWidget() {
return Container(
padding: EdgeInsets.all(16.0),
color: Colors.blueAccent,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(25.0)),
side: BorderSide(color: Colors.white))),
child: Row(
children: <Widget>[
Padding(padding: EdgeInsets.only(left: 12)),
Icon(Icons.arrow_back, color: Colors.black54),
Padding(padding: EdgeInsets.only(left: 12)),
Flexible(
fit: FlexFit.loose,
child: searchBar(),
),
Icon(
Icons.search,
color: Colors.black54,
),
Padding(padding: EdgeInsets.only(right: 12)),
],
)),
);
}
Widget searchBar() {
return Container(
height: 52,
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 12, right: 12),
child: TextField(
decoration: new InputDecoration.collapsed(
border: InputBorder.none,
filled: false,
hasFloatingPlaceholder: false,
hintText: 'Search here',
hintStyle: TextStyle(color: Colors.black54),
),
),
);
}
Widget listWidget(AsyncSnapshot<List<Project>> snapshot) {
return Scaffold(
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
listItem(snapshot.data[index]);
},
itemCount: snapshot.data.length));
}
Widget listItem(Project project) {
return Card(
elevation: 6.0,
child: Column(
children: <Widget>[Text('Project ID'), Text('Project Name')],
),
);
}
class EmptyAppBar extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
return Container();
}
@override
Size get preferredSize => Size(0.0, 0.0);
}
UPDATE: I made some changes to FutureBuilder:
Widget futureBuilder() {
return FutureBuilder<List<Project>>(
future: _getProjects(),
builder: (context, snapshot) {
debugPrint('Builder');
switch (snapshot.connectionState) {
case ConnectionState.done:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return listWidget(snapshot);
break;
default:
debugPrint("Snapshot " + snapshot.toString());
}
},
);
}
I am getting this error now:
I/flutter ( 4054): _FutureBuilderState<List<Project>>#67dc4):
I/flutter ( 4054): A build function returned null.
I/flutter ( 4054): The offending widget is: FutureBuilder<List<Project>>
I/flutter ( 4054): Build functions must never return null. To return an empty space that causes the building widget to
I/flutter ( 4054): fill available room, return "new Container()". To return an empty space that takes as little room as
I/flutter ( 4054): possible, return "new Container(width: 0.0, height: 0.0)".
And SnapShot in default is returning this Snapshot AsyncSnapshot>(ConnectionState.waiting, null, null)
Upvotes: 2
Views: 8197
Reputation: 4849
I/flutter ( 4054): A build function returned null.
You always need to return a widget from a builder, no matter what:
Widget futureBuilder() {
return FutureBuilder<List<Project>>(
future: _getProjects(),
builder: (context, snapshot) {
debugPrint('Builder');
switch (snapshot.connectionState) {
case ConnectionState.done:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return listWidget(snapshot);
break;
default:
debugPrint("Snapshot " + snapshot.toString());
return Container() // also check your listWidget(snapshot) as it may return null.
}
},
);
}
The issue is, debugPrint('Builder') is called even before _getProjects() returns result, so snapshot is passed as null in my ListView Widget.
This is normal, think for example if you have a long task ( a request or some really long calculation), you will need to display a loading until that future is "done". Also, putting something to be executed on a "future", means that it is ok for you to have it executed "later on", otherwise you need to use await.
Upvotes: 2
Reputation: 889
You need to check if there is any data returned by the AsyncSnapshot (snapshot).
Add a if
statement like this:
if(snapshot.hasData && !snapshot.hasError) {
//rest of your code
}
else {
//show progress indicator or error...
}
Upvotes: 2