how to initState and get data from where firebase - flutter

i try to create many account and create blog and every account see blog that post by itself. how to init state where in firebase. I declare variable uid in class and get it from initstate and how to use where in firebase i try to mix it with streambuilder
i declare uid in class and get data by this
void inputData() {
final User? user = auth.currentUser;
setState(() {
uid = user!.uid;
// print('uid =======> $uid');
});
}
my initstate
#override
initState() {
super.initState();
inputData(); }
final Stream<QuerySnapshot> animals = FirebaseFirestore.instance
.collection('animal')
.orderBy('createdate')
// .where('uid', isEqualTo: uid)
.snapshots();
after i use where here .I got error
The instance member 'uid' can't be accessed in an initializer.
Try replacing the reference to the instance member with a different expression
StreamBuilder<QuerySnapshot>(
stream: animals,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
return Text('Something Went Wrong!');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('Loading');
}
final data = snapshot.requireData;
return Container(
margin: EdgeInsets.only(top: 65),
child: ListView.builder(
shrinkWrap: true,
itemCount: data.size,
itemBuilder: (context, index) {
});
return ListTile(
title: Text(
'${data.docs[index]['animalName']}',
),
subtitle: Text(
'${data.docs[index]['animalDetail']}',
),
onLongPress: () async {
await processDeleteContent(context, data, index);
},
);
},
),
);
},
),
anyway to use where in streambuilder

Your uid is set in initState() that calls after the class is initialized. So you get uid before it is set. Try to write your animals with get so it runs code written there only when you get animals but not on initialization:
Stream<QuerySnapshot> get animals => FirebaseFirestore.instance
.collection('animal')
.orderBy('createdate')
.where('uid', isEqualTo: uid)
.snapshots();

Related

How to list favorites/bookmarks in Flutter with Firestore

I have a collection called Stuff that holds a title. Think it like a Twitter post.
{
'stuffID': string
'title': string
'details': string
}
And I have a favorites collection, that hold who favorite the which post. A user can favorite multiple stuff.
{
'userID': string
'stuffID': string
}
From second collection, I want to get all stuffID's that current user favorite. And I want to use those to get rest of the information from first collection. In summary, I want to list all stuff's that user favorite. Like a bookmark list.
I thought I must use two StreamBuilder for achieving this. But I couldn't make it work.
Here is what I manage to do:
#override
Widget build(BuildContext context) {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final userID = user!.uid;
var resultStream = FirebaseFirestore.instance
.collection('favorites')
.where("userID", whereIn: [userID]).snapshots();
return StreamBuilder<QuerySnapshot>(
stream: resultStream,
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot1) {
if (snapshot1.hasError) {
return Text('Something is wrong.');
}
if (snapshot1.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
snapshot1.data!.docs.map((DocumentSnapshot document1) {
Map<String, dynamic> data1 =
document1.data()! as Map<String, dynamic>;
print(data1['stuffID']);
Query _stuffStream = FirebaseFirestore.instance
.collection('Stuffs')
.where('stuffID', isEqualTo: data1['stuffID']);
return StreamBuilder<QuerySnapshot>(
stream: _stuffStream.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot2) {
if (snapshot2.hasError) {
return Text('Something is wrong.');
}
if (snapshot2.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
//showing the data
children:
snapshot2.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
String stuffID = data['stuffID'];
return ListTile(
title: Text(data['title']),
subtitle: Text(data['details']),
);
}).toList(),
);
});
});
return const Center(child: CircularProgressIndicator());
});
}
When I use this code, app stucks at loading screen:
I'm trying to work it for two days but all my attempts have failed. Can you please help?
I did more and more research day after day and found the answer. I created the code based on here. Note that I changed the design of my collection in Firebase. But it completely unrelated to rest of code. I just wanted to go with more efficient way to store the data.
In summary, I'm fetching the stuffID from favorites collection. And using that stuffID to get stuffs.
#override
Widget build(BuildContext context) {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final userID = user!.uid;
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('favorites')
.doc(userID)
.collection(userID)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text("Loadsing...");
return Column(children: [
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
//return buildListItem(context, snapshot.data.documents[index]);
return ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Stuffs')
.where('stuffID',
isEqualTo: snapshot.data!.docs[index]
['stuffID']) //seçilen döküman
.snapshots(),
builder: (context, snap) {
if (!snap.hasData) return const Text("Loading...");
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
snap.data!.docs[index]['stuffImage']),
),
title: Text(snap.data!.docs[index]['title']),
subtitle: Column(
children: <Widget>[
Text(snap.data!.docs[index]['details']),
],
),
);
}),
],
);
}),
]);
},
);
}

The getter 'length' was called on null. Receiver: null Tried calling: length Previous solutions didn't work for me

my question has definitely been asked before here but none of the solutions worked for me, hence i thought I should post my code here. I am building a todo app and Im stuck on the FutureBuilder widget I am getting the following error when running the code below. I have tried adding "AsyncSnapshot" in "builder: (context, snapshot)" but still getting the error. Could someone come to my rescue please?
The getter 'length' was called on null. Receiver: null Tried calling: length
Expanded(
child: FutureBuilder(
initialData: [],
future: _dbHelper.getTasks(),
builder: (context, AsyncSnapshot snapshot) {
return ScrollConfiguration(
behavior: NoGlowBehaviour(),
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return TaskCard(
title: snapshot.data[index].title,
);
},
),
);
},
),
)
],
Here is where the getTasks() method was initialised:
class DatabaseHelper {
Future<Database> database() async {
return openDatabase(
join(await getDatabasesPath(), 'todo.db'),
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
'CREATE TABLE tasks(id INTEGER PRIMARY KEY, title TEXT, description TEXT)',
);
},
version: 1,
);
}
Future<void> insertTask(Task task) async {
Database _db = await database();
await _db.insert('task', task.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
Future<List<Task>> getTasks() async {
Database _db = await database();
List<Map<String, dynamic>> taskMap = await _db.query('task');
return List.generate(taskMap.length, (index) {
return Task(
id: taskMap[index]['id'],
title: taskMap[index]['title'],
description: taskMap[index]['description']);
});
}
}
While using FutureBuilder consider checking snapshot state like bellow.
It is only possible to build list with items while future done with fetching and contains data.
I prefer checking
ConnectionState
Error
Data
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Text("loassding");
else if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done)
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
// final word = snapshot.data![index];
print(snapshot.data!.length);
print(snapshot.data![index].model![0].word);
return Column(
children: [
// Text("${word.model!.length} ${word.model![0].word!} "),
],
);
});
else if (snapshot.hasError) {
return Text(
snapshot.error.toString(),
);
} else
return Text("something else");
},
);
Does it solve in your case?
I know this is a late reply, but your query is not returning any data because that table does not exist. You have a typo in you retrieve code. You are trying to load data from "task". The same goes for you insert function. They both need to change to change to "tasks".
in your insertTask method change the line:
await _db.insert('tasks', task.toMap(),
in your getTasks method change the line:
List<Map<String, dynamic>> taskMap = await _db.query('tasks');

I am failing to get data from cloud firestore while using flutter

At first, when i started writing my calls to get data from firestore, it worked. But when i tried writing more docs to my collection, it failed to bring data for the docs i recently added. Then, when i deleted the first one i added, i stopped receiveing data from firestore all together. I have tried several methods, but have all ended in failure.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class collect extends StatefulWidget {
#override
_collectState createState() => _collectState();
}
class _collectState extends State<collect>
{
Future _data;
void initState()
{
super.initState();
_data = getStuff();
}
Future getStuff()
async {
var firestore = FirebaseFirestore.instance;
QuerySnapshot qn = await firestore.collection("buses").get();
return qn.docs;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _data,
builder: (_, snapshot)
{
if(snapshot.connectionState == ConnectionState.waiting)
{
return Center(
child:Text("Loading")
);
}
else if(snapshot.connectionState == ConnectionState.done)
{
return ListView.builder(itemCount: snapshot.data.length,itemBuilder:(_, index)
{
return Container(
child: ListTile(
title: Text(snapshot.data[index].data()["name"].toString()),
subtitle: Text(snapshot.data[index].data()["price"].toString()),
),
);
});
}
},
),
);
}
}
```![enter image description here](https://i.stack.imgur.com/L7FqF.jpg)
Define your database call as,
Future getStuff() async {
var docs;
await FirebaseFirestore.instance
.collection("buses")
.get()
.then((querySnapshot) {
docs = querySnapshot.docs;
});
return docs;
}
Then use the FutureBuilder in the build() function as,
return Scaffold(
body: Center(
child: FutureBuilder<dynamic>(
future: getStuff(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
return Container(
child: ListTile(
title: Text(
snapshot.data[index].data()["name"].toString()),
subtitle: Text(
snapshot.data[index].data()["price"].toString()),
),
);
});
} else {
return CircularProgressIndicator();
}
},
),
),
);
I wrapped the FutureBuilder inside a Center just for clarity, you may remove that Center widget.

StreamBuilder ListView returns empty list from Firestore on first load

In the app I'm building as part of the registration process a sub-collection, with up to 100 docs, is created for each 'User' document.
I'm trying to show these sub-collection documents in a StreamBuilder.
I have a curious bug that I can't resolve. The StreamBuilder doesn't display the data when the user first views it. Instead it returns an empty list.
I can see that the documents have been correctly generated within the sub-collection. The data is being set on a page before the page with the StreamBuilder. Even if there were latency I would have thought the new docs would have just started appearing within StreamBuilder.
Firebase console view
The StreamBuilder does display the data as expected if the app is restarted - or if the user logs out and logs in again.
Below is the code I'm using:
Stream<QuerySnapshot> provideActivityStream() {
return Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
}
...
Widget activityStream() {
return Container(
padding: const EdgeInsets.all(20.0),
child: StreamBuilder<QuerySnapshot>(
stream: provideActivityStream(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
if(snapshot.data == null) {
return CircularProgressIndicator();
}
if(snapshot.data.documents.length < 1) {
return new Text(
snapshot.data.documents.toString()
);
}
if (snapshot != null) {
print('$currentUser.userId');
}
if (
snapshot.hasData && snapshot.data.documents.length > 0
) {
print("I have documents");
return new ListView(
children: snapshot.data.documents.map((
DocumentSnapshot document) {
return new PointCard(
title: document['title'],
type: document['type'],
);
}).toList(),
);
}
}
)
);
}
Edit: Adding main build as per comment request
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text("Home"),
actions: <Widget>[
],
bottom: TabBar(
tabs: [
Text("Account"),
Text("Activity"),
Text("Links"),
],
),
),
body: TabBarView(
children: [
accountStream(),
activityStream(),
linksStream()
]
)
),
);
}
}
Attempts I've made to solve
I initially thought it was a connection error so created a series of cases based on switch (snapshot.connectionState). I can see that ConnectionState.active = true so thought adding a new document in Firestore might have an effect but does nothing.
I tried the following to make the initial stream constructor asynchronous. It fails to load any data.
Stream<QuerySnapshot> provideActivityStream() async* {
await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
}
I've tried removing the tabcontroller element - e.g. just having a single page - but that doesn't help either.
I've tried accessing the data using both a DocumentSnapshot and a QuerySnapshot. I have the problem with both.
I'm sure this is very straightforward but stuck on it. Any help greatly appreciated. Thanks!
It is not fetched by using any one of those query snapshot and document snapshot
You should query at first using Querysnapshot and then retrieve the information to a Documentsnapshot
Yes, it may take a few seconds to load the document the solution is correct that you should async and await function
Instead of streamBuilder, I suggest you use Direct snapshot
we can load the document snapshot in the initstate of a statefullWidget
works when your class is a statefullWidget and the problem is also related to the state
...
bool isLoading;
List<DocumentSnapshot> activity =[];
QuerySnapshot user;
#override
void initState() {
print("in init state");
super.initState();
getDocument();
`` }
getDocument() async{
setState(() {
isLoading = true;
});
user= await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.getDocuments();
activity.isEmpty ? activity.addAll(user.documents) : null;
setState(() {
isLoading = false;
});
}
//inside Widget build(BuildContext context) { return Scaffold( in your body
//section of scaffold in the cointainer
Container(
padding: const EdgeInsets.all(20.0),
child: isLoading ?
CircularProgressIndicator(),
:ListView.builder(
itemCount: global.category.length,
itemBuilder: (context, index) {
return PointCard(
title: activity[index].data['title'],
type: activity[index].data['type'],
);//pointcard
}
),//builder
),//container
We can also give a try for the following
QuerySnapshot qs;
Stream<QuerySnapshot> provideActivityStream() async{
qs= await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
return qs;
}//this should work
but as per the basics of streambuilder if the above piece didn't work
then there is another
QuerySnapshot qs;
Stream<QuerySnapshot> provideActivityStream() async* {
qs= await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
yield qs;
}//give this a try
tl;dr
Needed to use setState to get the Firebase currentUser uid to be available for the widgets
Needed to use AutomaticKeepAliveClientMixin to work correctly with TabBar
I think using the Provider package may be a better way of persisting the user's state but didn't in solving this problem
Explanation
My code gets the currentUser uid with a Future. As per the the SO answer here that's a problem because the widgets will all be built before FirebaseAuth can give back the uid. I initially tried to use initState to get the uid but that has exactly the same synchronous problem. Calling setState from the function to call the FirebaseAuth.instance allowed the widget tree to update.
I'm placing this widget within a TabBar widget. My understanding is that everytime a tab is removed from view it's disposed of so rebuilt when returned. This was causing further state issues. API docs for the AutomaticKeepAlive mixin are here
Solution code
With added comments in the hope they're helpful for someone else's understanding (or someone can correct my misunderstanding)
activitylist.dart
class ActivityList extends StatefulWidget {
// Need a stateful widget to use initState and setState later
#override
_ActivityListState createState() => _ActivityListState();
}
class _ActivityListState extends State<ActivityList>
with AutomaticKeepAliveClientMixin<ActivityList>{
// `with AutomaticKeepAliveClientMixin` added for TabBar state issues
#override
bool get wantKeepAlive => true;
// above override required for mixin
final databaseReference = Firestore.instance;
#override
initState() {
this.getCurrentUser(); // call the void getCurrentUser function
super.initState();
}
FirebaseUser currentUser;
void getCurrentUser() async {
currentUser = await FirebaseAuth.instance.currentUser();
setState(() {
currentUser.uid;
});
// calling setState allows widgets to access uid and access stream
}
Stream<QuerySnapshot> provideActivityStream() async* {
yield* Firestore.instance
.collection("users")
.document(currentUser.uid)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
padding: const EdgeInsets.all(20.0),
child: StreamBuilder<QuerySnapshot>(
stream: provideActivityStream(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if(snapshot.hasError) return CircularProgressIndicator();
if(snapshot.data == null) return CircularProgressIndicator();
else if(snapshot.data !=null) {
return new ListView(
children: snapshot.data.documents.map((
DocumentSnapshot document) {
return new ActivityCard(
title: document['title'],
type: document['type'],
startDateLocal: document['startDateLocal'],
);
}).toList(),
);
}
},
)
);
}
}
home.dart
...
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text("Home"),
actions: <Widget>[
],
bottom: TabBar(
tabs: [
Text("Account"),
Text("Activity"),
Text("Links"),
],
),
),
body: TabBarView(
children: [
accountStream(),
ActivityList(), // now calling a stateful widget in an external file
linksStream()
]
)
),
);
}
}

Can't combine firestore stream

So, i want to write query like this
... where from = x or to =x
I can't find any documentation about using where condition. So, i using StreamZip
#override
void initState() {
getEmail();
stream1 = databaseReference
.collection("userChat")
.where("from", isEqualTo: userId)
.orderBy("messageDate", descending: true)
.snapshots();
stream2 = databaseReference
.collection("userChat")
.where('to', isEqualTo: userId)
.orderBy("messageDate", descending: true)
.snapshots();
}
and here is my StreamBuilder
StreamBuilder(
stream: StreamZip([stream1, stream2]),
builder: (context, snapshot) {
print(snapshot.data.documents);
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
return new Flexible(
child: new ListView.builder(
controller: _scrollController,
padding: new EdgeInsets.all(8.0),
reverse: false,
itemBuilder: (context, index) {
print("Time to show data");
List rev = snapshot
.data.documents.reversed
.toList();
MessageFromCloud messageFromCloud =
MessageFromCloud.fromSnapshot(
rev[index]);
return new ChatMessage(
data: messageFromCloud,
userFullname: userFullname,
userId: userId,
roomId: documentId);
},
itemCount: (messagesCloud != null)
? messagesCloud.length
: 0,
),
);
}
}),
When i run it, i get this error
Class 'List' has no instance getter 'documents'.
Receiver: _List len:2 Tried calling: documents
Did i miss something?
StreamZip - emits lists of collected values from each input stream
It means that your snapshot.data is a List.
Would suggest checking out this answer: Combine streams from Firestore in flutter