Reputation: 742
I'm trying to pass the data to another screen using Provider
, but it seems I'm always passing on the same data unless I sort the List and then pass the different data (meaning I'm probably switching the index by sorting the list so that is why it's passing different data now). In short, I call the API, populate the list, setting up the provider too for the next page, and on click I list out the the information from the previous screen, but the problem is I display the same item always unless I sort the list. Here is the code:
Calling the API and displaying the list:
var posts = <RideData>[];
var streamController = StreamController<List<RideData>>();
@override
void initState() {
_getRideStreamList();
super.initState();
}
_getRideStreamList() async {
await Future.delayed(Duration(seconds: 3));
var _vehicleStreamData = await APICalls.instance.getRides();
var provider = Provider.of<RideStore>(context, listen: false);
posts = await _vehicleStreamData
.map<RideData>((e) => RideData.fromJson(e))
.toList();
streamController.add(posts);
provider.setRideList(posts, notify: false);
}
bool isSwitched = true;
void toggleSwitch(bool value) {
if (isSwitched == false) {
posts.sort((k1, k2) => k1.rideId.compareTo(k2.rideId));
} else {
posts.sort((k1, k2) => k2.rideId.compareTo(k1.rideId));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
TextButton(
child: Text('sort ascending'),
onPressed: () {
setState(() {
toggleSwitch(isSwitched = !isSwitched);
});
}),
Container(
height: 1000,
child: StreamBuilder<List<RideData>>(
initialData: posts,
stream: streamController.stream,
builder: (context, snapshot) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Column(
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 15.0),
child: Text(
'Ride #${snapshot.data[index].rideId}',
),
),
FlatButton(
textColor: Colors.blue[700],
minWidth: 0,
child: Text('View'),
onPressed: () {
// here is where I pass the data to the RideInfo screen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RideInfo(
rideId: snapshot
.data[index].rideId,
)));
},
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'${snapshot.data[index].pickupTime}',
),
Text(
'${snapshot.data[index].jobArrived}',
),
],
),
],
);
},
);
}),
),
],
),
),
),
);
}
After pressing the View button and passing the data to another screen (RideInfo):
class RideInfo extends StatefulWidget {
static const String id = 'ride_info_screen';
String rideId;
RideInfo({@required this.rideId});
@override
_RideInfoState createState() => _RideInfoState();
}
class _RideInfoState extends State<RideInfo> {
String rideID = '';
@override
void initState() {
super.initState();
setState(() {
rideID = widget.rideId;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'Ride #$rideID',
),
),
body: SafeArea(
child: SingleChildScrollView(
child: Consumer<RideStore>(
builder: (context, rideStore, child) {
return Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
RideData rides = rideStore.getRideByIndex(index);
return Column(
children: [
Expanded(
flex: 2,
child: Column(
children: [
Text(
"PICK UP",
),
// here I display the pickUpTime but it is always the same and I wanted to display the time based on the ID
Text(
'${rides.pickupTime}AM',
),
],
),
),
],
);
}),
],
);
},
),
),
),
);
}
}
The data (pickUpTime
in this case) doesn't change when I press to see the View of a single item, but like I said, when I change the order of the list with the sort method, then I get the different data.
Here is the Provider model:
class RideStore extends ChangeNotifier {
List<RideData> _rideList = [];
List<RideData> get rideList => _rideList;
setRideList(List<RideData> list, {bool notify = true}) {
_rideList = list;
if (notify) notifyListeners();
}
RideData getRideByIndex(int index) => _rideList[index];
int get rideListLength => _rideList.length;
}
How do I display the correct information based on the ID from the List that I pressed and passed in the Ride Info screen so it doesn't give back always the same data? Thanks in advance for the help!
Upvotes: 0
Views: 1995
Reputation: 2229
The offending code is in RideInfo
:
ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
RideData rides = rideStore.getRideByIndex(index);
The index
is always 1
, so you are always showing the first RideData
. There are various options to fix it, e.g. pass the index
, or even pass the RideData
, to the RideInfo
constructor:
class RideInfo extends StatefulWidget {
static const String id = 'ride_info_screen';
String rideId;
final int index;
RideInfo({@required this.rideId, @required this.index, Key key})
: super(key: key) {
and:
RideData rides = rideStore.getRideByIndex(widget.index);
I have some additional comments on the code. Firstly, the ListView
is serving no purpose in RideInfo
, so remove it.
Secondly, there is no need to construct the streamController
and to use StreamBuilder
in the parent form. Your list is available in the RideStore
. So your parent form could have:
Widget build(BuildContext context) {
var data = Provider.of<RideStore>(context).rideList;
...
Container(
height: 1000,
child:
// StreamBuilder<List<RideData>>(
// initialData: posts,
// stream: streamController.stream,
// builder: (context, snapshot) {
// return
ListView.builder(
shrinkWrap: true,
itemCount: data.length,
I hope these comments help.
Edit:
It is simple to edit your code to use FutureBuilder
. Firstly, make _getRideStreamList
return the data it read:
_getRideStreamList() async {
...
return posts;
}
Remove the call to _getRideStreamList
in initState
and wrap the ListView
in the FutureBuilder
that invokes _getRideStreamList
:
Container(
height: 1000,
child: FutureBuilder(
future: _getRideStreamList(),
builder: (ctx, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
var data = snapshot.data;
return ListView.builder(
...
);
}
},
),
),
This displays the CircularProgressIndicator
while waiting for the data.
Note that this is a quick hack - you do not want to read the data everytime that the widget rebuilds. So _getRideStreamList
could check if the data has already been read and just return it rather than rereading.
Upvotes: 1