I would like the appBar title of my Scaffold to display the total number of items (length) in a Firebase Query at launch, but it keeps returning Instance of 'Future<int>'. How can I fix this?
Here's my code:
Query itemList = FirebaseFirestore.instance
.collection('simple');
Future<int> itemCount() async => FirebaseFirestore.instance
.collection('simple')
.snapshots()
.length;
...
return Scaffold(
appBar: AppBar(
title: Text("# Items: " + itemCount().toString()),
// DISPLAYS: # Items: Instance of 'Future<int>'
Unfortunately, it displays Instance of 'Future<int>'. What do I have to change to obtain the item count (length) and have that show-up in the title text?
Thank you!
You can use a FutureBuilder like this :
AppBar(
title: FutureBuilder<String>(
future: itemCount(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
return Text("# Items: " + snapshot.data.toString());
}else {
Text("No data");
}
}),
)
You are calling a "Future" function, thats the function return a Future so you cant display it like that, you need to use an await (if you are in async function) or a .then() (if you'r not in async function).
The best way to print it in your case is to use a FutureBuilder or the keyword await.
After some experimenting, I came to the conclusion that I need to use StreamBuilder. Here's the solution that fixed it:
appBar: AppBar(
title: StreamBuilder(
stream: itemList.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("# Items: ...");
}
else if (snapshot.hasData) {
return Text("# Items: ${snapshot.data?.docs.length ?? 0}");
}
else {
return const Text('# Items: 0');
}
}, // Item Builder
), // Stream Builder
), // App Bar
Screenshot of completed project with # Items in appBar title:
The floating action button adds items to the list and onTap deletes the selected item from the list. The title remains updated.
I hope this helps someone in the future.
Related
I am trying to debug the following method, somehow the none of the breakpoints get hit. The one in the catch block also doesn't get hit. I fail to understand what is happening.
_getWorkout(workoutId) async {
try {
StreamBuilder<QuerySnapshot>(
stream: await FirebaseFirestore.instance
.collection("workouts")
.doc(workoutId)
.collection("exercises")
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
//doesn't go here---
print('SNAPSHOT DATA = ' + snapshot.data.toString());
if (!snapshot.hasData) {
//doesn't go here---
return const Text("There are no exercises");
}
//doesn't go here---
return DataTable( ...
],
rows: _getExercises(snapshot),
);
});
} on Exception catch (_, e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
duration: Duration(milliseconds: 1000),
),
);
}
}
StreamBuilder is a Widget that can convert a stream of user defined objects, to widgets.
You Should include inside Widget build as a widget
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child:StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("workouts")
.doc(workoutId)
.collection("exercises")
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
//doesn't go here---
print('SNAPSHOT DATA = ' + snapshot.data.toString());
if (!snapshot.hasData) {
//doesn't go here---
return const Text("There are no exercises");
}
//doesn't go here---
return DataTable();
}),
),
);
}
You need to return the StreamBuilder from _getWorkout, which you aren't. You are effectively returning null, which will cause flutter to not execute the StreamBuilder at all.
Always setting return types on your methods will help in avoiding oversights like that one.
I was now trying for days to retrieve my firestore values, but no luck so posting it here.
I have a Firestore database and some data. I want to retrieve this with the help of Flutter.
This is what I have been doing.
So I have a Flutter screen where it shows a simple 3-dot dropdown in the AppBar.
It has two options: edit and cancel.
What I want is, when I press edit, it should open a new screen and should pass the data that I retrieved from firestore.
This is where I have edit and cancel dropdown (3 dots) and calling the a function (to retrieve data and open the new screen).
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(widget.news.headline.toUpperCase()),
actions: <Widget>[
PopupMenuButton<String>(
onSelected: (value) {
_open_edit_or_delete(value); // caling the function here
},
itemBuilder: (BuildContext context) {
return {'Edit', 'Delete'}.map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
},
),
],
),
body: _get_particular_news(widget.news),
);
}
and this is the open_edit_or_delete function it is calling. But it doesn't open up (navigate) to the screen I am calling.
open_edit_or_delete(String selectedOption) {
News news;
Visibility(
visible: false,
child: StreamBuilder(
stream: FireStoreServiceApi().getNews(),
builder: (BuildContext context, AsyncSnapshot<List<News>> snapshot) {
if (snapshot.hasError || !snapshot.hasData) {
Navigator.push(
context, MaterialPageRoute(builder: (_) => FirstScreen(news:news)));
return null;
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
news = snapshot.data[index];
},
);
}
},
));
}
And in case you need the FireStoreServiceApi().getNews(), here it is as well.
// get the news
Stream<List<News>> getNews() {
return _db.collection("news").snapshots().map(
(snapshot) => snapshot.documents
.map((doc) => News.fromMap(doc.data, doc.documentID))
.toList(),
) ;
}
Can someone please help me?
You are not passing data correctly to your fromMap method.
You can access data using doc.data['']
If you have data and documentID property in it then following will work.
News.fromMap(doc.data.data, doc.data.documentID))
I don't know your fromMap method and i also don't what your snapshot contains, if this did not work for you then add them too.
I am fetching articles from HackerNews API using Bloc Pattern and Streams.
I am loading all the articles and presenting in the UI with the help of a stream builder, and this works fine.
Now I wrapped the article fetching Stream builder with the new loading StreamBuilder.
Now when the loading stream builder has true (means it is loading) it shows a circular progress indicator or else, it shows the child (Article List wrapped with a Stream Builder).
This works fine. But it is bugging me that I have wrapped Stream builder inside a stream builder. I know I can take help of rxdart but I am just not sure how.
I tried to add a loader with the help of snapshot.hasData or not but that didn't work, so I decided to create another stream and subject that takes a bool and tells the UI if it is loading or not.
Code fetching data int the bloc:
_getAndUpdateArticles(StoryType storyType) {
_isLoadingSubject.add(true);
getListIds(storyType).then((list) {
getArticles(list.sublist(0, 10)).then((_){
_articleSubject.add(UnmodifiableListView(_articles));
_isLoadingSubject.add(false);
});
});
}
UI:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: StreamBuilder(
stream: widget.hnBloc.isLoading,
builder: (context, snapshot) {
if (snapshot.data) {
return Center(child: CircularProgressIndicator());
} else {
return StreamBuilder<UnmodifiableListView<Article>> (
initialData: UnmodifiableListView<Article>([]),
stream: widget.hnBloc.article,
builder: (context, snapshot) => ListView(
children: snapshot.data.map(_buildItem).toList(),
),
);
}
},
),
.........
EDIT
I have tried this, but this isn't working:
StreamBuilder<UnmodifiableListView<Article>> (
initialData: UnmodifiableListView<Article>([]),
stream: widget.hnBloc.article,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data.map(_buildItem).toList(),
);
} else {
return CircularProgressIndicator();
}
}
),
I Don't think there is a complete way to avoid nested StreamBuilders. I personally wouldn't consider it a bad practice, but it will definitely lead to more build.
In your case, You can modify your hnBloc to emit a single state that can be a loading state or data state , thereby eliminating the need for a nested StreamBuider.
eg.
StreamBuilder<HnState>(
stream: hnBloc.currentState,
initialData: HnLoadingState(),
builder: (context, snapshot) {
if (snapshot.data is HnLoadingState) {
return Center(child: CircularProgressIndicator());
}if (snapshot.data is HnDataState) {
return ListView(
children: snapshot.data.map(_buildItem).toList(),
),
}
},
)
This pattern is very common when using the flutter_bloc package. You can see a basic example of this here to understand it better.
I'm populating a ListView from Streambuilder and want to show the length/nr of documents in the AppBar title. Right now I'm calling SetState everytime there's a change in the stream. It works but "feels" kinda resource heavy. Any ideas?
Thanks in advance.
Best,
/j
StreamBuilder(
stream: Firestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
appBarTitle = snapshot.data.documents.length;
Future.delayed(Duration.zero, () {
setState(() {
});
});
},
);
you can wrap title of appBar with Stream builder to update your screen title like this code
AppBar(title: StreamBuilder<Object>(
stream: bloc.myStream,
builder: (context, snapshot) {
return yourCustomWidget();
}
)
I am trying to do lay loading listview with live web service response in flutter can anyone help me? how to achieve this?
You should parse data in background.
Create a method to fetch data:
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response =
await client.get('https://jsonplaceholder.typicode.com/photos');
// Use the compute function to run parsePhotos in a separate isolate
return compute(parsePhotos, response.body);
}
Add your fetch method in builder of FutureBuilder.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: FutureBuilder<List<Photo>>(
future: fetchPhotos(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? PhotosList(photos: snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
);
}
}
Look full example: https://flutter.io/cookbook/networking/background-parsing/