I have an animated list, the build function is included in a stack from the body of the app.
Whenever somethings updates in the database, the streambuilder doesn't rebuild..
items is set in the init by the function:
Stream<List<Task>> getTasks(){
try {
Firestore.instance
.collection("lists")
.document(tasklist.postID)
.collection("tasks")
.snapshots();
}
on Exception {
error();
}
return ref.snapshots().map((list) =>
list.documents.map((doc) => Task.fromFireStore(doc)).toList());
}
Widget _buildTasksList() {
return new Expanded(
child: new StreamBuilder(
stream: items,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
}
return new AnimatedList(
initialItemCount: tasks.length,
key: _listKey,
itemBuilder: (context, index, animation) {
print(index);
return new TaskRow(
task: listModel[index],
animation: animation,
listModel: listModel,
tasklist: tasklist,
onChange: () => _onChange(listModel[index]),
);
},
);
},
)
);
}
had the wrong initialItemCount, should have been items.length
Related
I'm trying to write full app for the first time as a beginner. I'm using getx in this app too.
I'm taking a split second RangeError (index): Invalid value: Valid value range is empty: 0 while using StreamBuilder. It is working fine after that split second red error page. How I am supposed to deal with this, should I use something related with getx to stream the data?
This is my StreamBuilder:
StreamBuilder(
stream: stream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasData) {
List<KTCardItem> collectionList = filterByTag(snapshot.data!.docs);
return PageView.builder(
controller: PageController(keepPage: true),
scrollDirection: Axis.vertical,
itemCount: collectionList.length,
itemBuilder: (BuildContext context, index) {
Future.delayed(const Duration(seconds: 3));
return NFTCardView(
index: index,
isFavorite: isFavoritedByUser(index),
ktCardItem: collectionList[index],
onFavChanged: () {
onFavoriteChanged(collectionList[index].eventId ?? "", index);
},
);
},
);
} else {
return const Center(child: CircularProgressIndicator());
}
// filter the list by choice of tag
},
);
I'm guessing that cause of this problem is the filterByTag method that I do before the return. filterByTag method:
List<KTCardItem> filterByTag(List<QueryDocumentSnapshot> snapshot) {
List<KTCardItem> collectionList = [];
for (var document in snapshot) {
Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
if (data["tags"].contains(_tag.value)) {
collectionList.add(KTCardItem.fromMap(data));
}
}
return collectionList;
}
I am using a FutureBuilder inside a StreamBuilder that updates the UI every time a document is added to the activity collection, to get some aditional data from Firestore. The problem is that the FutureBuilder returns a SizedBox widget while the ConnectionState is waiting causing the all the cards to dissapear for a second. I would like to avoid this flickering since it causes a bad ui experience for users.
Is there a way to query the required user data in the activity stream so it all returns at once that way I can remove the FutureBuilder?
If not ... what would be a solution for this?
activityStream() {
return FirebaseFirestore.instance
.collection('activity')
.orderBy('timestamp', descending: true)
.limit(55)
.snapshots();
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 65.0,
);
}
StreamBuilder<QuerySnapshot>(
stream: activityStream(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const Center(child: CircularProgressIndicator());
default:
final activityContent = snapshot.data?.docs
.map((data) => ActivityModel.fromFirestore(data))
.toList();
return Scrollbar(
controller: widget.scrollController,
child: ListView.builder(
shrinkWrap: true,
controller: widget.scrollController,
itemCount: activityContent!.length,
itemBuilder: (context, i) {
return FutureBuilder(
future: FirebaseFirestore.instance
.collection('users')
.where('uid', whereIn: activityContent[i].players)
.get(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 65.0,
);
}
final users = snapshot.data!.docs.map((e) {
return UserModel.fromFirestore(e);
}).toList();
return MyWidget(
users: users,
);
},
);
},
),
);
}
},
);
I have a screen (ProductAddScreen.dart) that tries to load data from firestore (products unpublished) but if the list is empty, I want to redirect to a new screen (ProductFormScreen.dart).
#override
Widget build(BuildContext context) {
return StreamBuilder<List<Product>>(
stream: context.watch<ProductService>().unpublished(),
initialData: [],
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Loading(color: Colors.green);
}
// ════════ Exception caught by widgets library ════════
// setState() or markNeedsBuild() called during build.
if (!snapshot.hasData) {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => ProductFormScreen()));
}
return Scaffold(
appBar: AppBar(
title: Text(Strings.productAddAppBarTitle),
),
body: ListView.separated(
itemBuilder: (context, index) {
final product = snapshot.data[index];
return ProductItemRow(
product: snapshot.data[index],
onTap: () => print('hello'),
);
},
separatorBuilder: (context, index) => Divider(height: 0),
itemCount: snapshot.data.length,
),
);
},
);
}
I come from react js and I think I am confused. How can I do this with Flutter?
As error states you are trying to navigate during build;
To avoid that could use post build callback:
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => ProductFormScreen()));
});
if(snapshot.hasData){
if(snapshot.data.yourList.length == 0){
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) =>
ProductFormScreen()));
}
return Scaffold(
//your design
);
}
Note :- In your model initialise your List like
List<YourListObject> list = [];
Future navigateToSubPage(context) async {
Navigator.push(context, MaterialPageRoute(builder: (context) => SubPage()));
Sample code to check the list is empty
return _items.isEmpty ? Center(child: Text('Empty')) : ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return _buildFilteredItem(context, index);
},
)
}
Scaffold(body: FutureBuilder(
future: fetchTracks(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.hasData)
{
ListView.builder(
scrollDirection: Axis.vertical,
itemExtent: 130.0,
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: trackes.length,
itemBuilder: (BuildContext context, int index) {
print("test");
return makeCard(snapshot.data[index]);
},
).build(context);
}
else
{
return Center(child: new CircularProgressIndicator());
}
} ));
When i call this Scaffold Future build will call my future function fetchTracks() and get the data in snapshot but it is not entering into itemBuilder function. So futurebuilder return NULL.
Please help me to solve .and Thank you in advance
You're missing a return before ListView.builder. If you don't return it, it won't build it.
FutureBuilder has different snapshot connectionstates which you must handle. Data on the stream is not available until ConnectionState equals done and hasData equals true.
_loadData(context)
{
showModalBottomSheet(
context: context,
builder: (BuildContext bc){
return FutureBuilder(
future: fetchTracks(),
builder: (BuildContext context, AsyncSnapshot<List<MyClass>> snapshot){
if (snapshot.connectionState!=ConnectionState.done)
{
return PleaseWaitWidget();
}
else if(snapshot.hasError)
{
DialogCaller.showErrorDialog(context,"future builder has an error").then((value){});
}
else if (snapshot.connectionState==ConnectionState.done)
{
if(snapshot.hasData){
List<Widget> list = snapshot.data.map((MyClass myClass){
return Card(
child:Wrap(children:<Widget>[
Row(children: [Text(myClass.field1)],),
]));}).toList();
return list;
}
}
});
});
}
I have a flutter app where a list is generated with ListView.Builder, and where the itemCount is the number of documents in a firestore collection. This works fine until a new document is added. When that happens I get the error (17 and 18 are just examples).
Invalid value: Not in range 0..17, inclusive: 18
I assume I would need to update the state when a new document is created, but I have no idea how i can call setState when that happens
Here is the relevant part of the code:
child: StreamBuilder(
stream: Firestore.instance.collection('contact').orderBy(sortby, descending: decending).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Container();
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_personer(context, snapshot.data.documents[index], index),
);
},
),
use setState?
StreamBuilder(builder: (context, snapshot) {
return snapshot.hasData == null ? Container() : _getListView(snapshot);
} , )
_getListView(snapshot) {
setState(() {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_personer(context, snapshot.data.documents[index], index),
);
});
}
StreamBuilder use QuerySnapshot so list data can change
example code :
StreamBuilder<QuerySnapshot>(
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Text('Loading...');
default:
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return ;
}).toList(),
);
}
},
)