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();
}
)
Related
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.
I think this situation is a little odd. I'm using two FutureBuilders inside one BuildContext. However, the second FutureBuilder is not working, which means the connectionState is always waiting, and I literally have no idea what's going on.
First FutureBuilder inside AppBar()
final appBar = AppBar(title: FutureBuilder(
future: futureThatWillReturnAString(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Searching location...");
}
return Text(snapshot.data);
},
));
Second FutureBuilder inside Widget build(BuildContext) {}
appBar: appBar,
body: FutureBuilder(
future: futureThatWillReturnAMap(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading lists");
}
return ListView.builder(...);
},
)
Only the AppBar will finish the future, while the second FutureBuilder remains in the loading state. Please tell me if I'm missing something or if there is something wrong in my implementation. Thanks!
When I update the database in console or app, the changes are not synced in the app. I thought it's because of orderBy and indexing settings, but commenting them didn't help. What's wrong with my code? How to set up auto-sync?
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: DrawerMenu(),
appBar: AppBar(
title: Text('Заявки'),
),
body: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('orders')
.where('companyId', isEqualTo: globals.companyId)
// .orderBy('dateAdded', descending: true)
// .orderBy('statusId')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return LoadingScreen();
default:
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) => GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Items(
order: snapshot.data.documents[index],
),
)),
child: OrderCard(
order: snapshot.data.documents[index],
)));
}
},
),
);
}
Its because you are just getting the snapshot as a stream once, but to get the actual changes when you modify in console or app you need to make a subscription to the stream. You need to .listen the stream of data so that you get notified when something change.
Also you may want to separate the logic of firebase its easier.
For example:
Create a Stream Controller:
final StreamController<DataType> _streamController = StreamController<DataType>();
Listen to firebase data and add it to stream:
Firestore.instance
.collection('orders')
.where('companyId', isEqualTo: globals.companyId)
.snapshots().listen((DocumentSnapshot snapShot){
_streamController.add(snapShot.documents);
});
Now you can listen to the stream from stream controller:
_streamController.stream;
It turned out that it's necessary to add custom indexes in firestore console when a query consists of .where().orderBy and so on.
I am trying to show Circular Progress Indicator while my data in Future Builder loads, I tried two methods to add it however both of them didn't work. How can I achieve the desired result?
My code
class _MyHomePageState extends State<MyHomePage>{
#override MyHomePage get widget => super.widget;
#override
Widget build(BuildContext context){
//To show the ListView inside the Future Builder
Widget createTasksListView(BuildContext context, AsyncSnapshot snapshot) {
var values = snapshot.data;
return ListView.builder(
itemCount: values == null ? 0 : values.length,
itemBuilder: (BuildContext context, int index) {
return values.isNotEmpty ? Ink(
.....
) : new CircularProgressIndicator(); //TRIED TO ADD CIRCULAR INDICATOR HERE
},
);
}
//Future Builder widget
Column cardsView = Column(
children: <Widget>[...
Expanded(
child: FutureBuilder(
future: //API CALL,
initialData: [],
builder: (context, snapshot) {
if (!snapshot.hasData) return Center(child: CircularProgressIndicator()); //CIRCULAR INDICATOR
return createTasksListView(context, snapshot);
}),
),
],
);
return Scaffold(
...);
}
}
Try with:
FutureBuilder(
future: //API CALL,
initialData: [],
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(child: CircularProgressIndicator()); //CIRCULAR INDICATOR
else
return createTasksListView(context, snapshot);
}),
),
When the value of the future is an empty list, which is your initialData, you are rendering a ListView with 0 items, so you cannot render a CircularProgressIndicator in its itemBuilder.
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.