Flutter widgets, how to use multiple async Future,. data loading questions - flutter

I have a basic app, loading data into a list view widget, I have a 2nd set of data I'd like to be able to reference
This kind of thing, but in the creation of the List Tile, I want to use data from another method called getOtherData() .. essentially to join the data but I'd rather not do it in sql/object creation..
child: FutureBuilder<List<Contact>>(
future: getContacts(),
builder: (BuildContext context,
AsyncSnapshot<List<Contact>> snapshot) {
return ListView(
children: snapshot.hasData
? snapshot.data
.map((e) =>[ ListTile(
leading: ExcludeSemantics(
child: CircleAvatar(
child: Text(e.daysSinceContacted
.toString()),
)),
title: Text(e.firstName),
subtitle: Text(DateTime.now()
.difference(e.lastContacted)
.inDays
.toString() +
" Days" +
":" +
e.lastContacted
.toIso8601String() +
getGroupName(e, groups)
.whenComplete((x) => x)),
enabled: true,
},
))))
.toList()
: []);
})),
So where I do
title: Text(e.firstName),
I'd like to do
title: Text(e.firstName + getOtherData(e.id)
I can't figure out how to deal with the Future<> returns from the db sync methods though...
basically, just how do I get data so that I can use it?

The easiest way might be to use another FutureBuilder.
title: FutureBuilder<String>(
future: getOtherData(e.id),
builder: (_, snapshot) {
if(!snapshot.hasData) return Container(); // or return something else while loading
final otherData = snapshot.data;
return Text(e.firstName + otherData);
},
),

Related

reading data from firebase firestore collection at stream builder

I got trouble with firebase fireStore.
There is a stream builder reading data from items collection.
Inside items collection there is some fields and another collections.
I haven't any problem with fields, the problem is with collection.
how to access those collections inside stream builder?
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: CallApi().finalReference(reference: widget.finalReference),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text('snapshot Error:${snapshot.error}'));
}
if (snapshot.hasData) {
var snapData = snapshot.data!.docs;
if (kDebugMode) {
print(snapData.length);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ListView.builder(
itemCount: snapData.length,
itemBuilder: (BuildContext context, int index) {
return ListItem(
mTitle: snapData[index].get('title') ?? '',
mSubTitle: snapData[index].get('address') ?? 'empty',
mPrice: snapData[index].get('price') ?? '',
mImageUrl: snapData[index].get('gallery')[0],
mOnTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsPage(
adsTitle: snapData[index].get('title'),
adsSubTitle: snapData[index].get('subTitle'),
gallery: snapData[index].get('gallery'),
specFTitle: snapData[index].get('gallery'),
),
),
);
},
);
},
),
),
],
);
}
return const Center(child: CircularProgressIndicator());
},
),
here is firebase
Reading data from Firestore is a shallow operation. When you read a document, its subcollection are not automatically read.
So if you want to get the data from the subcollections of the current document, you will have to start a new read operation for that. If you want to show that data in the UI, you can use a new, nested StreamBuilder or FutureBuilder for that.

(Flutter) StreamBuilder returns only null

I am trying to create a "CategoryStream" to update the UI based on the users choice.
This is my stream:
import 'dart:async';
class CategoryStream {
StreamController<String> _categoryStreamController =
StreamController<String>();
closeStream() {
_categoryStreamController.close();
}
updateCategory(String category) {
print("Update category in stream was called");
print("the new category is: " + category);
_categoryStreamController.sink.add(category);
}
Stream<String> get stream => _categoryStreamController.stream;
}
And my StreamBuilder looks like this:
return StreamBuilder<String>(
stream: CategoryStream().stream,
builder: (context, snapshot) {
return Container(
color: Colors.white,
child: Center(
child: Text(snapshot.data.toString()),
),
);
},
);
So when the User choses a new category, i try to update the Stream like this:
CategoryStream().updateCategory(currentChosenCategory);
Whatever i do, the result is always null. Although the right category is displayed in the print() function...
What am i missing?
Maybe i need to work with a StreamProvider? Not a StreamBuilder? Because i am adding the data from a Parent-Widget to a Child-Widget of a Child-Widget..
By default, the value of a StreamController is null. You need to set at initialData or add data to the stream before you call snapshot.data in your StreamBuilder:
final CategoryStream _categoryStream = CategoryStream();
return StreamBuilder<String>(
stream: _categoryStream.stream,
initialData: "",
builder: (context, snapshot) {
return Container(
color: Colors.white,
child: Center(
child: Text(snapshot.data), //toString() is unnecessary, your data is already a string
),
);
},
);

How to get a document snapshot index

I'm new to flutter and I'm trying to pass a firestore document snapshot to another class.
I passed to the Profile class a snapshot document, and I want to indicate the index of my document, but I don't know how to get it
I have this
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: ((searchString != null) &&
(searchString.trim() != ""))
? Firestore.instance
.collection('pazienti')
.where("searchIndex",
arrayContains: searchString)
.snapshots()
: Firestore.instance
.collection('pazienti')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
return ListView(
children: snapshot.data.documents
.map((DocumentSnapshot document) {
return Card(
elevation: 10.00,
margin: EdgeInsets.all(0.50),
child: ListTile(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => Profile(miaquery: snapshot.data.documents[????])));
}
,
leading: CircleAvatar(
backgroundColor:
Colors.blueGrey.shade800,
),
title: Text(document['cognome'] +
" " +
document['nome']),
subtitle: Text(document['cognome'] +
" " +
document['nome']),
),
);
}).toList(),
);
}
})),
],
),
)
My problem is essentially here
Navigator.push(context, MaterialPageRoute(builder: (context) => Profile(miaquery: snapshot.data.documents[XXXX]))
How can I get the index of the document from the map I used?
Thank you very much for your help
You just want to pass document on which tap, so you can simply pass document which you are getting from map method.
Snapshots from a query don't have a numeric index. The results from a query could change at any time between queries, and the system can not guarantee that any sort of index would be stable.
If you want to pass a document to another function, pass its unique document ID. The receiver can then query the document directly, perhaps from local cache without requiring a billed read operation at the server.
var listIndex= snapshot.data.documents.map((e) => e.data['key']);
var passIndex = listIndex.toList().indexOf(doc.data['key']);
print(passIndex);
You can simply pass the index when assigning the list
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return ListView(
padding: const EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildItem(context, data, snapshot.indexOf(data))).toList(),
);
}

I am getting error while using stream builder in flutter

i am making a mobile app using flutter. And i am using stream builder for this screen. I am not getting the point where i am wrong in the code. Can you please help me in this. I am sharing code and screenshot for this particular row which is causing problem
var timeSelected = 'Click here';
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
'Time Slot:',
style: TextStyle(color: Colors.white),
),
Spacer(),
GestureDetector(
onTap: () {
_asyncInputDialog(context);
//_displayDialog();
},
child: StreamBuilder(stream: cartManager.getTimeSlotSelected,
initialData: timeSelected,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData){
timeShow(snapshot,);
}
else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(
child: Container(
child: Text('Select time slot'),
),
);
},)
),
],
),
This alert dialog will show when i click on the text of row:
_asyncInputDialog(
BuildContext context,
) {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Center(child: Text('Available Time Slot')),
content: TEAlertDialogContent(),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
}
When i got the value from showdialog i will store the value in streamcontroller that is present in CartManager.
static StreamController<Timeslot> timeSlotController = BehaviorSubject();
timeSlotSelected(Timeslot time){
timeSlotController.sink.add(time);
}
get getTimeSlotSelected{
return timeSlotController.stream;
}
And we call the above method in stream property of streamcontroller and get the snapshot. This is the method which was called when our snapshot has data:
Widget timeShow(AsyncSnapshot<Timeslot> snapshot ) {
timeSelected = '${snapshot.data.firstTimeSlot}-${snapshot.data.secondTimeSlot}';
timeslotid = snapshot.data.id.toString();
return Text(timeSelected);
}
But i am getting error: type 'BehaviorSubject' is not a subtype of type 'Stream'
Please let me know where i am wrong. I had also shared a screen shot of screen showing this error too.
As your error states, you are trying to pass a type Timeslot to a Stream builder expecting a stream of type String. You must check which one is correct (String or Timeslot) and use the same type on both sides.
Apparently, your problem is in the timeSelected variable. Where is it defined? If this is a String, the Stream builder will infer that your stream is of type String, which is not true. You must set this variable as a Timeslot, since this is your stream type.
Also, you have an error in your code. You have to return a widget to be rendered if snapshot has data. Check the code below:
StreamBuilder(stream: cartManager.getTimeSlotSelected,
initialData: timeSelected,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData){
return timeShow(snapshot,);
}
else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(
child: Container(
child: Text('Select time slot'),
),
);
},)

How to get data from the FutureProvider in flutter

I'm trying to implement local database support in my flutter app which is being managed using Provider, now I want to make the retrieving of data obey the state management pattern, but I've been failing to.
I've tried to make a traditional Provider to achieve this but the app got stuck in a loop of requests to the database, so after some search I found the FutureProvider, but I cant find how can I get a snapshot from the data being loaded
class _ReceiptsRouteState extends State<ReceiptsRoute> {
List<Receipt> receipts = [];
#override
Widget build(BuildContext context) {
return FutureProvider(
initialData: List(),
builder: (_){
return DBProvider().receipts().then((result) {
receipts = result;
});
},
child: Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).history),
),
body: Container(
child: ListView.builder(
itemBuilder: (context, position) {
final item = receipts[position];
return ListTile(
title: Text(item.date),
);
},
),
),
),
);
}
}
now my app is running as I want but not as how it should run, I used FutureBuilder to get the data from the database directly but I know it should come through the provider so I want to make it right
FutureProvider exposes the result of the Future returned by builder to its descendants.
As such, using the following FutureProvider:
FutureProvider<int>(
initialData: 0,
builder: (_) => Future.value(42),
child: ...
)
it is possible to obtain the current value through:
Provider.of<int>(context)
or:
Consumer<int>(
builder: (context, value, __) {
return Text(value.toString());
}
);
In my example I used the create parameter of FutureProvider to request the API, then then I used Consumer to get the results of the API.
FutureProvider(
create: (_) => peopleService.getAllSurvivor(),
child: Consumer<List<Survivor>>(builder: (context, survivors, _) {
return survivors == null
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: survivors.length,
itemBuilder: (context, index) {
var survivor = survivors[index];
return ListTile(
title: Text(survivor.name),
subtitle: Text(survivor.gender),
leading: Icon(Icons.perm_identity),
);
},
);
})));