I want my app to create a ListView.builder with user's data that are fetched from backend API with provider and http package.
Provider's Method For Fetching data (Works fine now):
Future<List<User>> fetchUsersList() async {
var resp = await UserService().getAllUsers();
if (resp.statusCode == 200) {
return userListFromJson(resp.body);
} else {
decodeErrorMessage(resp);
return _usersList = [];
}
}
My Code for Creating the ListView.builder:
class _StaffViewState extends State<StaffView> {
bool _searchaMode = false;
bool _fetchingData = false;
List<User> _userList = List<User>();
@override
Widget build(BuildContext context) {
Provider.of<UserProvider>(context).fetchUsersList().then(
(data) => this._userList = data);
return Consumer<UserProvider>(
builder: (context, providerData, _) => Scaffold(
appBar: AppBar(
title: _searchaMode == false
? Text('Staff\s List')
: TextField(
autofocus: true,
cursorColor: Colors.white,
onSubmitted: (String value) async {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Thanks!'),
content: Text('You typed "$value".'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('OK'),
),
],
);
},
);
},
style: TextStyle(
color: Colors.white,
),
decoration: InputDecoration(
hintText: 'Search . . .',
hintStyle: TextStyle(color: Colors.white),
border: InputBorder.none,
),
),
actions: [
_searchaMode == false
? IconButton(
onPressed: () {
setState(() {
_searchaMode = true;
});
},
icon: Icon(Icons.search),
)
: IconButton(
onPressed: () {
setState(() {
_searchaMode = false;
});
},
icon: Icon(
Icons.cancel,
color: Colors.tealAccent,
),
),
RaisedButton.icon(
onPressed: () async {},
color: Colors.yellow.shade900,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(100.0),
topLeft: Radius.circular(100.0),
),
),
icon: Icon(
Icons.add,
size: 25.0,
color: Colors.white,
),
label: Text(
'Add User',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
),
drawer: AppSideNav(),
body: _fetchingData == true
? pr.show()
: ListView.builder(
itemCount: _userList.length,
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: .9,
color: Colors.yellow.shade700,
),
),
),
child: ListTile(
leading: Image.network(
providerData.getUsersList()[index].image != null
? providerData.getUsersList()[index].image
: 'https://via.placeholder.com/150'),
title: Center(
child:
Text(providerData.getUsersList()[index].email)),
subtitle: Center(
child: Text(providerData.getUsersList()[index].nid)),
trailing:
providerData.getUsersList()[index].isAuthority == true
? Text('Authority')
: providerData.getUsersList()[index].isStaff ==
true
? Text('Staff')
: Text('General User'),
contentPadding: EdgeInsets.symmetric(horizontal: 20.0),
visualDensity: VisualDensity.adaptivePlatformDensity,
isThreeLine: false,
onTap: () {},
),
);
},
),
),
);
}
}
My Data is loading to build the list. But the problem is an error occurs that says setState() or markNeedsBuild() called during build.
My question is what is the actual way of populating data with the provider package where the dataFetching method will be executed first and than the list will be build.
Instead of calling your fetch function at the start of the build
function or in the initState
function, you can simply use the FutureBuilder
widget. This way you can specify exactly what to show when it is still loading and what when finished, without adding more and more member variables.
Your modified code could then look like this:
class _StaffViewState extends State<StaffView> {
bool _searchaMode = false;
@override
Widget build(BuildContext context) {
return Consumer<UserProvider>(
builder: (context, providerData, _) => FutureBuilder(
future: providerData.fetchUsersList(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Text("Loading...");
}
List<User> userList = snapshot.data;
return Scaffold(
appBar: AppBar(
title: _searchaMode == false
? Text('Staff\s List')
: TextField(
autofocus: true,
cursorColor: Colors.white,
onSubmitted: (String value) async {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Thanks!'),
content: Text('You typed "$value".'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('OK'),
),
],
);
},
);
},
style: TextStyle(
color: Colors.white,
),
decoration: InputDecoration(
hintText: 'Search . . .',
hintStyle: TextStyle(color: Colors.white),
border: InputBorder.none,
),
),
actions: [
_searchaMode == false
? IconButton(
onPressed: () {
setState(() {
_searchaMode = true;
});
},
icon: Icon(Icons.search),
)
: IconButton(
onPressed: () {
setState(() {
_searchaMode = false;
});
},
icon: Icon(
Icons.cancel,
color: Colors.tealAccent,
),
),
RaisedButton.icon(
onPressed: () async {},
color: Colors.yellow.shade900,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(100.0),
topLeft: Radius.circular(100.0),
),
),
icon: Icon(
Icons.add,
size: 25.0,
color: Colors.white,
),
label: Text(
'Add User',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
),
drawer: AppSideNav(),
body: ListView.builder(
itemCount: userList.length,
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: .9,
color: Colors.yellow.shade700,
),
),
),
child: ListTile(
leading: Image.network(
providerData.getUsersList()[index].image != null
? providerData.getUsersList()[index].image
: 'https://via.placeholder.com/150'),
title: Center(
child: Text(
providerData.getUsersList()[index].email)),
subtitle: Center(
child:
Text(providerData.getUsersList()[index].nid)),
trailing: providerData
.getUsersList()[index]
.isAuthority ==
true
? Text('Authority')
: providerData.getUsersList()[index].isStaff ==
true
? Text('Staff')
: Text('General User'),
contentPadding:
EdgeInsets.symmetric(horizontal: 20.0),
visualDensity: VisualDensity.adaptivePlatformDensity,
isThreeLine: false,
onTap: () {},
),
);
},
),
);
},
),
);
}
}