I'm making a to-do list with Flutter and Firebase Firestore, but I have a slight problem. Whenever I add a new task, snapshot.data becomes null. If I do not add a check for snapshot.connectionState and render a different widget according to that, I get the following error: NoSuchMethodError (NoSuchMethodError: The getter 'docs' was called on null.
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('tasks')
.doc(widget.uid)
.collection('mytasks')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: SizedBox(
height: 100, width: 100, child: CircularProgressIndicator()),
);
} else {
> final docs = snapshot.data.docs;
return ListView.builder(
itemCount: docs.length,
itemBuilder: (ctx, index) {
final currTask = docs[index];
return Dismissible(
direction: DismissDirection.startToEnd,
key: UniqueKey(),
onDismissed: (_) async {
FirebaseFirestore.instance
.collection('tasks')
.doc(widget.uid)
.collection('mytasks')
.doc(currTask['id'])
.delete();
.
.
.
I don't want to have to display a CircularProgressIndicator or an empty screen. I want the tasks to remain visible and seamlessly add new tasks. How can I achieve that? I know what I asked for in the question title might be a little silly, what should I do instead?
Related
I am currently attempting to make a user search list within an alert dialog, which will query users from the project's database based on the user's search input. I am doing this in Android Studio, using Flutter's native language (Dart) and Firebase Cloud Firestore. I have the search bar itself working, but for some reason, whenever I try to actually get the results from the database, my code will access the stream for the Streambuilder being used, but will never touch the actual builder, skipping it entirely. What exactly am I doing wrong here?
The function responsible for creating the alert dialog:
Future createAlertDialog(BuildContext context){
String userToSearch = '';
bool showUsers = false;
return showDialog(context: context, builder: (context){
return AlertDialog(
title: const Text("Search for a user:"),
content: StatefulBuilder(
builder: (context, setState) => Container(
child: CupertinoSearchTextField(
onChanged: (value) => {
setState(() {
showUsers = true;
}),
showUsers
? Expanded(
child: StreamBuilder(
stream: FireStoreMethods().searchUsers(value),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.connectionState ==
ConnectionState.none) {
return const Center(child: Text("Internet error"));
}
if (snapshot.hasError) {
return const Center(
child: Text("Something went wrong."),
);
}
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
return ListTile(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ProfileScreen(
uid: snapshot.data!.docs[index]['uid'],
),
),
),
leading: CircleAvatar(
backgroundImage: NetworkImage(
snapshot.data!.docs[index]['photoUrl'],
),
radius: 16,
),
title: Text(
snapshot.data!.docs[index]['username'],
),
);
},
);
},
),
)
: const Expanded(child: Text("error"))
}
),
),
)
);
});
}
Function responsible for querying the database:
Stream searchUsers(String userInput){
String? currentUserID = FirebaseAuth.instance.currentUser?.uid;
//String? valueFromFirebase = '';
Stream s = FirebaseFirestore.instance.collection('users').where('username', isGreaterThanOrEqualTo: userInput).orderBy('username', descending: false).snapshots();
return s;
}
To be clear, I expected this code to create a list of users from the database, under the search bar in the alert dialog, containing the users that match the current input. I tried debugging, changing the positioning of certain lines of code, and comparing and contrasting my code to code I found all over the internet. The actual result that I received was the ability to use the search bar and have the input saved properly, but literally nothing happens after pressing enter. No list is rendered, no error is thrown, and the program continues like nothing happened.
You need to place StreamBuilder inside widget tree to make it visible. Currently having inside onChanged which is just callback method for textFiled.
Future createAlertDialog(BuildContext context) {
String userToSearch = '';
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("Search for a user:"),
content: StatefulBuilder(
builder: (context, setState) => Column(
children: [
CupertinoSearchTextField(
onChanged: (value) {
setState(() {
userToSearch = value;
});
},
),
userToSearch.isNotEmpty
? Expanded(
child: StreamBuilder(
stream: FireStoreMethods().searchUsers(userToSearch),
...........
),
)
: Text("Empty")
],
),
),
);
});
I have already done everything for the setup I'm sure. I connected my project in the firebase console, and I was able to get my app to run after an await firebase initialize (options : options...etc.) once I finished with the flutterfire CLI config.
But now I can't get the actual value from my json file in the realtime database emulator. All I can get is null. Maybe I am not referring to the json right, but my json right now is just:
{ "hello" : "hi" }
so I don't think that's the problem. This is how I'm calling the database.
final _database = FirebaseDatabase.instance.ref();
and I'm using a future builder.
child:FutureBuilder(
future: _database.child('http://{$host_number}/?ns=nimble-autumn-{$project_id_number}/hello').get(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
//shrinkWrap: true,
scrollDirection: Axis.vertical,
physics: AlwaysScrollableScrollPhysics(),
itemCount: gocrazy.length,
itemBuilder: (BuildContext context, int index) {
return PostCard(
status: public[0], comments: [snapshot.toString()]);
//rags[index]);
},
);
}
else {
return Column(
children: <Widget>[
SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(),
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
),
]
);
};
And the UI will complete with the if statement because I am using .toString() and this what I get (if I use .value it will cause null error).
It says invalid Firebase Database data path. So what is the valid way to put it? I am using:
'http://{$host_number}/?ns=nimble-autumn-{$projectid}/hello'
but using 'nimble-autumn-projectid/hello' or 'hello' will just have the futurebuilder hang indefinitely.
When I delete the item from the list, then I go back and refresh the page, RefreshIndicator seems not working(The animation is working but not refreshing the page). I have searched a lot about this problem. I tried everything I found on the web but none of them worked for me. The problem is that I have the method of _refresh to call this method onRefresh but it didn't work. I debugged the code to see whether the refresh method is being called. As far as I see it seems it is being called because I see refresh method is called on the debug console. The ListView.builder also has the physics property and it's not shrunk. I saw one more solution that suggests adding items that fill the whole screen. I added as many items as I can but it didn't work. So any suggestions? I am suspecting from the FutureBuilder that is a parent of the ListView.builder, I tried to cover the FutureBuilder too but it didn't work either.
class _DraftsState extends State<Drafts> {
final SQFLiteHelper _helper = SQFLiteHelper.instance;
#override
void initState() {
print('init state is called');
super.initState();
_helper.getForms();
}
Future<void> _refresh() async {
print('refresh method is called');
await _helper.getForms();
}
//TODO: RefreshIndicator not working.
//TODO:When the list changed nothing is happening until the draft section is rebuilt
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<FormData>?>(
future: _helper.getForms(),
builder:
(BuildContext context, AsyncSnapshot<List<FormData>?> snapshot) {
if (snapshot.hasData && snapshot.data!.isEmpty) {
return const Center(
child: Text("Henüz kaydedilmiş taslak bulunmamaktadır."));
}
if (snapshot.hasError) {
return Center(
child: Text(
'Bir şeyler ters gitti.',
style: TEXT_STYLE,
));
}
if (snapshot.connectionState == ConnectionState.done) {
return RefreshIndicator(
backgroundColor: Colors.grey[700],
color: LIGHT_BUTTON_COLOR,
onRefresh: _refresh,
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return CustomListTile(
formData: snapshot.data![index], index: index);
},
),
),
);
}
return const Center(
child: CircularProgressIndicator(),
);
}),
);
}
}
Future<void> _refresh() async {
print('refresh method is called');
setState(() {
await _helper.getForms();
});
}
use setState in your refresh function. coz you need to reload the build method. or I think you can use setState like this.
Future<void> _refresh() async {
print('refresh method is called');
await _helper.getForms();
setState(() { });
}
I created 2 different collections (users and follow).
Now I want to:
Fetch document which exist inside the follow's collection("list") according by their id.
Fetch users data in the first collection where currentUid = doc.id (for the second collection).
Display data in the ListTile.
First collection
await FirebaseFirestore.instance.collection("users").doc(currentUid).set({"name":username,"photoUrl":url,"uid":currentUid});
Second collection
await FirebaseFirestore.instance.collection("follow").doc(currentUid).collection("list").doc(otherId);
I used this but it doesn't work properly
body:StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("follow")
.doc(user!.uid)
.collection("list")
.snapshots(),
builder: (context, snapshot1) {
if (!snapshot1.hasData) {
return Container();
} else {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("users")
.where("uid",
isEqualTo:
snapshot1.data!.docs.map((e) => e.id).toList())
.snapshots(),
builder: (context, snapshot2) {
if (!snapshot2.hasData) {
return Container();
} else {
return ListView(
children: snapshot2.data!.docs.map((e) {
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(e.get('url')),
radius: 30,
),
title: Text(e.get('username')),
);
}).toList(),
);
}
},
);
You should use the in query instead of the equal to query.
An in query returns documents where the given field matches any of the
comparison values.
The syntax for the in query in the cloud_firestore is this:
.where(field, whereIn: listOfFields)
Solution:
Change this:
.where("uid", isEqualTo: snapshot1.data!.docs.map((e) => e.id).toList())
to this:
.where("uid", whereIn: snapshot1.data!.docs.map((e) => e.id).toList())
The relevant error-causing widget was
StreamBuilder. I've given the link of the project repo in github .
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection("/chats/2DGQmBssJ4sU6MVJZYc0/messages")
.orderBy('createdAt', descending: true)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
Center(child: CircularProgressIndicator());
final documents = snapshot.data.documents;
return ListView.builder(
reverse: true,
itemCount: documents.length,
itemBuilder: (context, index) {
return MessageBubble(
documents[index]['text'],
documents[index]['userID'] == user.uid,
documents[index]['userID'],
documents[index]['imageurl']);
},
);
},
);
}
}
Github link of the project
Try the following:
if (snapshot.connectionState == ConnectionState.waiting)
Center(child: CircularProgressIndicator());
else if(snapshot.connectionState == ConnectionState.active){
final documents = snapshot.data.documents;
return ListView.builder(
reverse: true,
itemCount: docs.length,
itemBuilder: (context, index) {
return MessageBubble(
docs[index]['text'],
docs[index]['userID'] == user.uid,
docs[index]['userID'],
docs[index]['imageurl']);
},
);
},
},
);
When the connectionState is active meaning with data being non-null, then retrieve the data. Also use docs since it seems you are using the new version of Firestore.
documents became docs
Edit
Now that I've become little better than before, so I must update this answer for better understanding.
Let's take this above example:
return ListView.builder(
reverse: true,
itemCount: documents.length,
itemBuilder: (context, index) {
return MessageBubble(
documents[index]['text'],
documents[index]['userID'] == user.uid,
documents[index]['userID'],
documents[index]['imageurl']);
},
);
},
);
Here, all the documents in that particular collection MUST contain these 3 fields, i.e, 'text', 'userID', and 'imageurl' to successfully build each child inside the listView.builder().
But, if any of these 3 fields is missing, or even miss-spelled in One or more documents, then it would throw an error like the above.
Will throw error:
Will throw error:
So, to solve that we need to find that particular document which has the field missing, or field miss-spelled and correct it.
Or, delete that document and keep only those documents which has correct fields.
(By the way, I'm using "Dark Reader" chrome extension for dark theme for firebase console, and every other website on internet.)
My Old Answer:
In my case I was using Firebase Firestore for my chat app, and while loading the chat screen, I was getting this error.
So in Firestore Database I had 15-20 messages, So I deleted most of them and leave just 2-3 messages. And then the Error disappeared.
I don't know happened here.
You create a widget and then don't use it and follow through with the rest of your logic:
if (snapshot.connectionState == ConnectionState.waiting)
Center(child: CircularProgressIndicator());
You need to stop there and return that widget from your builder:
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());