I am getting users data from firebase by stream builder,
And I am rendering those data using Listview.builder(),
one problem is that when I get snapshot from firestore my all items in listview.builder() is rebuilding.
how do I prevent this thing reRendering or Rebuilding()
Hear is my Code
class GetUser extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("// <1> Use StreamBuilder");
return StreamBuilder<QuerySnapshot>(
// <2> Pass `Stream<QuerySnapshot>` to stream
stream: FirebaseFirestore.instance
.collection('users')
.orderBy('createdAt', descending: false)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// <3> Retrieve `List<DocumentSnapshot>` from snapshot
final List<DocumentSnapshot> documents = snapshot.data.docs;
//documents.forEach((doc) => print(doc));
print(documents.length);
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
addAutomaticKeepAlives: true,
addRepaintBoundaries: false,
itemCount: documents.length,
itemBuilder: (context, i) {
return Items(
doc: documents[i],
);
},
),
)
],
),
);
} else if (snapshot.hasError) {
return Text("Error");
} else {
return Text("Waiting");
}
});
}
}
//this Item class is invoked every time I add a user to the collection
class Item extends StatefulWidget {
final DocumentSnapshot doc;
const Item({Key key, this.doc}) : super(key: key);
#override
_ItemState createState() => _ItemState();
}
class _ItemState extends State<Item> {
#override
Widget build(BuildContext context) {
print("Build called ${widget.doc.id}");
return Text("${widget.doc["full_name"]}--${widget.doc.id}");
}
}
Related
I'm trying to fetch documents from my firebase DB and use them to create a social media feed. Here I'm trying to get the length of the fetched collection but I cannot manage to call the variable. Any help would be appreciated. Example code
class LoadDataFromFirestore extends StatefulWidget {
#override
_LoadDataFromFirestoreState createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
#override
void initState() {
super.initState();
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('fish');
Future<void> getData() async {
// Get docs from collection reference
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
final allData = querySnapshot.docs.map((doc) => doc.data()).toList();
print(allData);
}
}
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: querySnapshot.docs.length,
itemBuilder: (BuildContext context, int index) {
return _postView();
},
),
);
}
}
First of all it is not ok to call future function in initstate, you need to use FutureBuilder like this:
class LoadDataFromFirestore extends StatefulWidget {
#override
_LoadDataFromFirestoreState createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
late CollectionReference _collectionRef;
#override
void initState() {
super.initState();
_collectionRef = FirebaseFirestore.instance.collection('fish');
}
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<QuerySnapshot>(
future: _collectionRef.get(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
QuerySnapshot? querySnapshot = snapshot.data;
return ListView.builder(
itemCount: querySnapshot?.docs?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
var data = querySnapshot?.docs?[index].data();
print("data = $data");
return _postView();
},
);
}
}
},
),
);
}
}
inside listview's builder you can use data to parse your data and use it.
You can use FutureBuilder like this:
class LoadDataFromFirestore extends StatefulWidget {
const LoadDataFromFirestore({super.key});
#override
State<LoadDataFromFirestore> createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
//TODO change Map<String, dynamic> with your data type with fromJson for example
Future<List<Map<String, dynamic>>> _getData() async {
final querySnapshot = await FirebaseFirestore.instance.collection('fish').get();
return querySnapshot.docs.map((doc) => doc.data()).toList();
}
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<Map<String, dynamic>>>(
future: _getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return _postView(/* Ithink you have to pass here your item like snapshot.data[index]*/);
},
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
}
class AdView extends StatefulWidget {
const AdView({Key? key, required String id}) : super(key: key);
final id = '2';
#override
_AdViewState createState() => _AdViewState();
}
class _AdViewState extends State<AdView> {
final _adService = NewsService();
Future<AdBanner?> futureAdd() async {
_adService.getAds('2');
}
Future<Categories?> futureCatergoriess() async {
_adService.getAllCategories();
}
#override
void initState() {
futureAdd();
futureCatergoriess();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.grey[200],
body: FutureBuilder(
future: Future.wait([futureCatergoriess(), futureAdd()]),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.hasData) {
final advertisements = snapshot.data![0];
return ListView.builder(
itemCount: advertisements!.length,
itemBuilder: (BuildContext context, int index) {
//return bannerListTile(advertisements, index, context);
return const Text('index');
});
} else {
if (snapshot.hasError) {
return NewsError(
errorMessage: '${snapshot.hasError}',
);
}
return const NewsLoading(
text: 'Loading...',
);
}
},
),
);
}
}
snapshot.data![0]; returning null value. I tried already many versions ([1] or snapshot.data.data but I cannot call the results.
I am using future.wait first time. There is no problem if I use any of API with traditional Future.builder.
any help?
after the advice of #ChristopherMoore I modified the code but the main problem is still continue. This code gives as output:
index
index
modified code
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([futureCatergoriess(), futureAdd()]),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.hasData) {
final advertisements = snapshot.data!;
return ListView.builder(
itemCount: advertisements.length,
itemBuilder: (BuildContext context, int index) {
//return bannerListTile(advertisements, index, context);
return const Text('index');
});
This original line gives this error:
final advertisements = snapshot.data![0];
The getter 'length' was called on null. Receiver: null Tried calling: length The relevant error-causing widget was FutureBuilder<List<Object?>> lib/view/ad_view.dart:37
i am new to flutter and trying to display data from a http post
referencing from [1]https://flutter.dev/docs/cookbook/networking/background-parsing and [2]https://flutter.dev/docs/cookbook/networking/fetch-data
i tried to display data on a futurebuilder but it keeps displaying this from the Text('${snapshot.data}')
[Instance of 'DashBoardBanner', Instance of 'DashBoardBanner', Instance of 'DashBoardBanner']
Builder
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<List<DashBoardBanner>> futureBanner;
#override
void initState() {
super.initState();
futureBanner = getBannerDataFromServer();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ListView(
children: [
Card(
child: FutureBuilder(
future: getBannerDataFromServer(),
builder: (context,snapshot){
if(snapshot.connectionState == ConnectionState.done){
if (snapshot.hasData) {
return Text('${snapshot.data}');
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
}
return const CircularProgressIndicator();
},
),
)
],
)),
);
}
}
Class and postreq
class DashBoardBanner {
final String MsgId;
final String MsgKey;
final String MsgPic;
const DashBoardBanner(
{required this.MsgId, required this.MsgKey, required this.MsgPic});
factory DashBoardBanner.fromJson(Map<String, dynamic> json) {
return DashBoardBanner(
MsgId: json['MsgId'] as String,
MsgKey: json['MsgKey'] as String,
MsgPic: json['MsgPic'] as String,
);
}
}
Future<List<DashBoardBanner>> getBannerDataFromServer() async {
final queryParameters = {
"ApiFunc": 'Banner',
"UserKey": getDeviceKey(),
"Token": getDeviceToken(),
"SubmitContent": json.encode({"MobileNo": getMobileNo1()})
};
final response = await http.post(
Uri.http('somesite.net', '/capi.aspx', queryParameters),
);
if (response.statusCode == 200) {
Map<String, dynamic> data = jsonDecode(response.body);
final splitoff = jsonEncode(data['RespContent']);
return compute(parseBanner, splitoff);
} else {
throw Exception('Failed to load Data');
}
}
List<DashBoardBanner> parseBanner(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed
.map<DashBoardBanner>((json) => DashBoardBanner.fromJson(json))
.toList();
}
Edit : i rebuilt the file replicating reference[1] and it finally displayed the data i needed, it seems the issue stem from not having this 2nd widget which return the obj back , however how do i combine the 2nd build widget into the first without needing the whole widget as having a whole build widget to return 1 line seems pointless?
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body:Container(
child: FutureBuilder<List<DashBoardBanner>>(
future: getBannerDataFromServer(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('An error has occurred!'),
);
} else if (snapshot.hasData) {
print(snapshot.data!.length);
return DashBoardBannersList(dashboardBanners: snapshot.data!); <--- original issue due to not having this
} else {
return CircularProgressIndicator();
}
},
),
),
);
}
}
class DashBoardBannersList extends StatelessWidget {
const DashBoardBannersList({Key? key, required this.dashboardBanners}) : super(key: key);
final List<DashBoardBanner> dashboardBanners;
#override
Widget build(BuildContext context) {
return Text(dashboardBanners[0].MsgId);
}
}
This error is caused because of the sound null safety
snapshot.data might be null for some requests so you can't access the array at a certain index cause it can be null.
If you know for sure snapshot.data exists you can use the ! operator to tell dart the variable is not null for sure like that:
snapshot.data![index];
You can also check if the data is null before accessing it like that:
if (snapshot.data != null) {
// do something with snapshot.data[index]
}
I recommed to read more about sound null safety here
Check the Firestore docs.
Inside snapshot.data, there's docs (every document of your collection).
The code is from there:
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
return ListTile(
title: Text(data['full_name']),
subtitle: Text(data['company']),
);
}).toList(),
);
},
);
}
The code above shows how to convert every doc (type DocumentSnapshot) to a JSON format (that can be represented with Map<String, dynamic>). To access to the doc id, you'll access with document.id, because it isn't inside the document.data() method.
You wanna retrieve a list of DashBoardBanner but you forget initialize the futurebuilder by adding a ListView.builder().
Try to use the following code idea :
FutureBuilder(
future: getBannerDataFromServer(http.Client()),
builder: (context, AsyncSnapshot snapshot) {
print(snapshot.hasData);
if (snapshot.hasError) {
return CircularProgressIndicator();
} else if (snapshot.hasData) {
return Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
var data = snapshot.data![index];
return DashBoardBannersList(dashboardBanners: data);
},),
),},
},)
I have a small problem, I need to retrieve into my list a collection retrieved by StreamBuilder from Firestore.
I am using snapshot.data.documents.lenght but once I add it I got error:
Class 'DocumentSnapshot' has no instance getter 'documents'.
Receiver: Instance of 'DocumentSnapshot'
Tried calling: documents
this is my code:
Stream<DocumentSnapshot> getDatabase() async* {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
yield* Firestore.instance
.collection('dataCollection')
.document(user.uid)
.snapshots();
}
#override
Widget build(BuildContext context,) {
return StreamBuilder(
stream: getDatabase(),
builder: (context, snapshot,) {
if (snapshot.data != null) {
return Column(
children: <Widget>[
Container(
height: 500,
child: ListView.builder(
shrinkWrap: true,
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
return Card(
color: Color(0xFF1f2032),
elevation: 15,
child: Text(
snapshot.data['phone']..
just change your code as following
Stream dataStream
then
#override
void initState() {
getDatabase().then((value) {
dataStream = value;
setState(() {});
});
super.initState();
}
the funcion getDatbase()
getDatabase() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
yield* Firestore.instance
.collection('dataCollection')
.document(user.uid)
.snapshots();
}
then
#override
Widget build(BuildContext context,) {
return StreamBuilder(
stream: dataStream,
builder: (context, snapshot,) {
if (snapshot.data != null) {
return Column(
children: <Widget>[
Container(
height: 500,
child: ListView.builder(
shrinkWrap: true,
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
return Card(
color: Color(0xFF1f2032),
elevation: 15,
child: Text(
snapshot.data['phone']..
Try this,
StreamBuilder(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<List<DocumentSnapshot>> snapshots) {
if (snapshots.connectionState == ConnectionState.active &&
snapshots.hasData) {
return Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshots.data.length,
itemBuilder: (BuildContext context, int index) {
//do something with snapshot.
}
}
return Container();
},
),
);
} else {
return Container();
}
},
),
Initialise your stream like this,
Stream<DocumentSnapshot> stream;
Future<dynamic> getDatabase() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
setState(() {
stream=Firestore.instance
.collection('dataCollection')
.document(user.uid)
.snapshots();
});
}
You can call getDatabase() in initState.
Update:-
This is your full code.
class DataCo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.blue,
),
body: Column(
children: [
CollectData(),
],
),
);
}
}
class CollectData extends StatefulWidget {
#override
_CollectDataState createState() => _CollectDataState();
}
class _CollectDataState extends State<CollectData> {
final String phone;
final String wife;
final String location;
_CollectDataState({this.phone, this.wife, this.location});
#override
void initState() {
super.initState();
getDatabase();
}
Stream<DocumentSnapshot> stream;
Future<dynamic> getDatabase() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
setState(() {
stream=Firestore.instance
.collection('dataCollection')
.document(user.uid)
.snapshots();
});
}
#override
Widget build(BuildContext context,) {
return StreamBuilder(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshots) {
if (snapshots.connectionState == ConnectionState.active &&
snapshots.hasData) {
return Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshots.data.length,
itemBuilder: (BuildContext context, int index) {
//do something with snapshot.
}
}
return Container();
},
),
);
} else {
return Container();
}
},
);
}
}
class NoData extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Text('No Data available'),
],
);
}
}
I am using the below widget and the CardItem class to fetch documents from the firestore database. Which is working fine. But When I reach the last document it shows the error as show in the this image . How should I resolve it?
WIDGET
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: widget._firestore.collection("posts").snapshots(),
builder: (context,snapshot){
//String itemTitle = snapshot.data.documents[index]['postContent'];
if (!snapshot.hasData){
return Text("Loading");
}
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index){
String itemTitle = snapshot.data.documents[index]['postContent'];
return CardItem(itemTitle:itemTitle);
});
},
);
CARDITEM
class CardItem extends StatefulWidget {
String itemTitle;
CardItem({this.itemTitle});
#override
_CardItemState createState() => _CardItemState();
}
class _CardItemState extends State<CardItem> {
bool ischecked = false;
#override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: Text(widget.itemTitle),
),
);
}
}
Use Ternary Operator
Text(widget.itemTitle !=null ? widget.itemTitle: 'on null add any default description')