Reputation: 1187
I'm trying to replace the ListView with new data but this is not behaving as expected. The main focus is on the
ListView.builder(
itemBuilder: (context, i) {
return Order(_orders[i]);
}
and
_getOrders() async {
String authToken = await Auth.getAuthToken();
http.Response response = await http.get(
API_URL + '?date=' + _formatDate(_activePeriod),
headers: {'x-auth': authToken});
var parsedJson = json.decode(response.body);
setState(() {
if (parsedJson.runtimeType.toString() != 'List<dynamic>') {
_orders = [];
} else {
_orders = parsedJson;
}
});
}
the first render is correct when the app starts but when I call the _changeMonth
method on Prev/Next button press which, in turn, calls the _getOrders
, it doesn't reload the ListView after I override the _orders
variable.
If the array has more items than the previous then I see the new items added at the end but the previous (first time rendered) items stay on the screen.
I can correctly see the new data but overriding the _orders
variable after the API call does not render the new data in the ListView. Do I need to do something more than using the setState()
?
Full code of main file. Order
class is defined in other file but I believe it is not required here.
class Home extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return HomeState();
}
}
class HomeState extends State {
final DateTime _thisMonth = DateTime.now();
DateTime _activePeriod = DateTime.now();
final String app = 'Orders';
List<dynamic> _orders = [];
bool isLoggedIn = false;
@override
initState() {
super.initState();
Auth.init().then((loggedIn) {
if (loggedIn) {
setState(() {
isLoggedIn = true;
});
_getOrders();
}
});
}
HomeState() {}
@override
build(BuildContext context) {
return MaterialApp(
title: app,
home: Scaffold(
appBar: new AppBar(
title: Text(app),
),
body: isLoggedIn
? ListView.builder(
itemBuilder: (context, i) {
return Order(_orders[i]);
},
itemCount: _orders.length,
)
: Auth.showLoginForm(),
persistentFooterButtons: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
FlatButton(
child: Text('Prev'),
onPressed: () {
_changeMonth(-1);
},
),
FlatButton(
child: Text('Next'),
onPressed: _activePeriod.month == _thisMonth.month
? null
: () {
_changeMonth(1);
},
),
],
)
],
),
);
}
_changeMonth(int increment) async {
setState(() {
_activePeriod =
DateTime(_activePeriod.year, _activePeriod.month + increment, 1);
_getOrders();
});
}
_formatDate(DateTime date) {
return '${date.year}/${date.month}/${date.day}';
}
_getOrders() async {
String authToken = await Auth.getAuthToken();
http.Response response = await http.get(
API_URL + '?date=' + _formatDate(_activePeriod),
headers: {'x-auth': authToken});
var parsedJson = json.decode(response.body);
//print(parsedJson);// new data appears fine
setState(() {
if (parsedJson.runtimeType.toString() != 'List<dynamic>') {
_orders = [];
} else {
_orders = parsedJson;
}
});
}
}
Upvotes: 0
Views: 80
Reputation: 1019
Try to add a key for Order
// constructor
Order(..., {Key key}): super(key: key)
// you should use unique key for stateful widgets, otherwise the list rebuild will be incorrect
itemBuilder: (context, i) {
return Order(_orders[i], Key(_orders[i].id));
},
You can use keys to control which widgets the framework matches up with other widgets when a widget rebuilds. By default, the framework matches widgets in the current and previous build according to their runtimeType and the order in which they appear. With keys, the framework requires that the two widgets have the same key as well as the same runtimeType.
Upvotes: 1