My question is if I can put a List in a Doc from Firebase.
Here is my Code:
Future getPosts() async {
var test = await FirebaseFirestore.instance
.collection("Profiles")
.doc(auth.currentUser.displayName)
.collection("Following")
.doc()
.get()
.asStream()
.toList();
print(test);
qn = await FirebaseFirestore.instance
.collection("Posts")
.doc() // here I want the string of All documents from the List
.collection("_Posts")
.get();
}
The Code is pretty shitty but yeah hope you can Help.
Here is a working code implementing both FutureBuilder() and StreamBuilder():
class PlayGround extends StatefulWidget {
const PlayGround({Key? key}) : super(key: key);
#override
_PlayGroundState createState() => _PlayGroundState();
}
class _PlayGroundState extends State<PlayGround> {
FirebaseFirestore _db = FirebaseFirestore.instance;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
//using a Future ( Fetch once )
Expanded(
child: FutureBuilder<List<PostsModel>>(
future: fetchUserPosts('someUID'),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return LinearProgressIndicator();
}
return ListView.builder(itemBuilder: (context, index) {
return Container(
child: Text(snapshot.data![index].title.toString()));
});
},
)),
//using a stream (Real Time)
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: _db
.collection('Users')
.doc('someUID')
.collection('Posts')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return LinearProgressIndicator();
}
List<PostsModel> _postsStream = snapshot.data!.docs
.map((e) =>
PostsModel.fromMap(e.data() as Map<String, dynamic>))
.toList();
return ListView.builder(itemBuilder: (context, index) {
return Container(
child: Text(_postsStream[index].title.toString()));
});
},
)),
],
),
);
}
//to get list of posts from Firestore
Future<List<PostsModel>> fetchUserPosts(userID) async {
var result = await _db
.collection('Users')
.doc(userID)
.collection('Posts')
// .orderBy("SomeThing")
.get();
List<PostsModel> postsModel =
result.docs.map((doc) => PostsModel.fromMap(doc.data())).toList();
return postsModel;
}
}
class PostsModel {
final String? title;
final num? likes;
PostsModel({
this.title,
this.likes,
});
factory PostsModel.fromMap(Map<String, dynamic> json) => PostsModel(
title: json["title"],
likes: json["likes"],
);
Map<String, dynamic> toMap() => {
"title": title,
"likes": likes,
};
}
I am creating a List in Flutter and displaying it in a Column, When I Run it is just Empty and when I print the list it just prints an Array
I/flutter (24613): []
I am using this code to create the List:-
myFunction() {
return StreamBuilder(
stream:
users.orderBy('timestamp', descending: true).limit(30).snapshots(),
builder: (context, snapshot) {
List<UserList> usersList = [];
snapshot.data.documents.forEach((doc) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
});
return Column (children: usersList);
}
),
}
This is My User Class:-
class User {
final String id;
final String username;
final String email;
final String photoUrl;
User({
this.id,
this.username,
this.email,
this.photoUrl,
});
factory User.fromDocument(DocumentSnapshot doc) {
return User(
id: doc.data()['id'],
username: doc.data()['username'],
email: doc.data()['email'],
photoUrl: doc.data()['photoUrl'],
);
}
}
The Code Is Showing No Errors and the Column Is not Displaying, Also When I print The length of the List it Shows it is Zero:-
I/flutter (24613): 0
What Could be The problem ??
I guess we need to tweak some of your code little bit to make the logic working. :)
builder param should be specified with Type otherwise it will be of type dynamic. To be in safer side in this case it will be QuerySnapshot. So,
builder: (context, snapshot) in your code becomes
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot).
Next, there is no need of looping through foreach and instead you can try something like below.
snapshot.data.docs.map((document) { .... }
snapshot.data.documents in your code is not valid way of getting the Firestore Documents. Please refer official doc
And you need to return a widget from builder which you have done correctly. But, by mistake you are passing the List<UserList> to Column which will be expecting List<Widget>
return Column (children: usersList);
Here I can see you are passing usersList which is of type List<UserList>. So you can replace Column with ListView or similar kind of other widget since, Column doesn't support scroll.
So combining all these bits and pieces you will get the below snippet.
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('users')
.orderBy('timestamp', descending: true)
.limit(30)
.snapshots(), // Just for simplicity.
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
//When there is no data returned from the firebase.
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView(
children: snapshot.data.docs.map((document) {
return Text("Title: " + document['username']);
}).toList(),
);
},
);
For simplicity, I have returned the Text widget. You can implement your own UI there.
NOTE : This is the basic working example and you need to fine tune accordingly like using model classes instead of directly accessing based on your requirements.
Your Code
myFunction() {
return StreamBuilder(
stream:
users.orderBy('timestamp', descending: true).limit(30).snapshots(),
builder: (context, snapshot) {
List<UserList> usersList = [];
snapshot.data.documents.forEach((doc) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
});
return Column (children: usersList);
}
),
}
It is because you have to await for the json to actually get parse to the dart model. Second thing is forEach method is synchronous it doesn't wait for the async operation to complete, this is the reason why your list is empty.
This SO question has lot of different ways to make a list work asynchronously in flutter.
Column shows data before fetching data, so it shows empty list. For this use setstate according to your state management type ("notifylisteners" in provider) after getting data, so by this the screen will be updated and column also shows the updated list.
I'm not very sure how you're handling the scope of the variable.
Here's my minimal reproducible code which can give you some idea on how to add the items to the list.
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
final List<Widget> _list = [FlutterLogo()];
#override
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (timer) {
if (timer.tick >= 2) timer.cancel();
setState(() => _list.add(FlutterLogo()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: _list),
);
}
}
As 'Ashutosh patole' said, 'forEach' method does not wait iteration's complete.
I think that because of this reason, although you made a 'usersList',
there is no data when build widget in 'usersList'.
To fix this, you'd better change from 'forEach' to 'for'.
void main() async {
List<String> data = [ 'a', 'b', 'c'];
List<String> result = [];
data.forEach((data) async {
await Future.delayed(Duration(seconds: 1));
result.add(data);
});
print(result);
await Future.delayed(Duration(seconds: 3));
print(result);
print('-----------------');
result = [];
for (var item in data) {
await Future.delayed(Duration(seconds: 1));
result.add(item);
}
print(result);
await Future.delayed(Duration(seconds: 3));
print(result);
}
In your code, you can change like below.
List<UserList> usersList = [];
for (var doc in snapshot.data.documents) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
}
Before calling the data, check all fields:
Firestore Docs
Add a print() to see where the problem
FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
//This
if (snapshot.hasError) {
return Text("Something went wrong");
}
print(snapshot.data);
//This
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Document does not exist");
}
print(snapshot.data);
//This
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>;
return Text("Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
);
This is what i typically use.Try out this! Please balance the brackets in the code
FutureBuilder(
future: users.orderBy('timestamp', descending: true).limit(30),
builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
List<User>ulist=snapshot.data;
return ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.only(top: 25,bottom: 35),
itemCount: evlist==null?0:evlist.length,
itemBuilder: (BuildContext context, int index) {
String evtime=evlist[index].fromdate.substring(11,16);
String ontime=evlist[index].fromdate.substring(0,16);
return Container(
decoration: BoxDecoration(
border: Border.all(width: 1.8,color: Colors.indigo[900]),
borderRadius: BorderRadius.circular(12.0),
color: Colors.grey[200]
),
margin:
const EdgeInsets.symmetric(horizontal: 18.0, vertical: 4.0),
child: ListTile(
leading: Icon(Icons.notifications),
title: Text(ulist[index].username.toString()),
subtitle:Text("next data"),
),
);
},
);
I need to convert a snapshot from cloud firestore to a list, i know this is unnecessary to show the data but i need it to reorder the data based in other parameters, this is my code
Stream chatRooms;
List item = [];
Widget chatRoomsList() {
return StreamBuilder(
stream: chatRooms,
builder: (context, snapshot) {
if (snapshot.hasData &&
!snapshot.hasError) {
item = [];
item = snapshot.data;
return ListView.builder(
itemCount: item.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ChatRoomsTile(
otherUserUid:item[index]['arrayUsers']
.replaceAll("[", "")
.replaceAll(widget.user.uid, "")
.replaceAll("]", "")
.replaceAll(",", "")
.replaceAll(" ", ""),
chatRoomId:
item[index]["chatRoomId"],
user: widget.user,
);
});
} else
return Container();
},
);
}
#override
void initState() {
getUserInfogetChats();
super.initState();
}
getUserInfogetChats() async {
DatabaseMethods().getUserChats(widget.user.uid).then((snapshots) {
setState(() {
chatRooms = snapshots;
});
});
}
and im getting this error
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following _TypeError was thrown building StreamBuilder<dynamic>(dirty, state: _StreamBuilderBaseState<dynamic, AsyncSnapshot<dynamic>>#48820):
type 'QuerySnapshot' is not a subtype of type 'List<dynamic>'
Change:
item = snapshot.data;
into this:
item = snapshot.data.documents;
documents should return a List<DocumentSnapshot>, so also change the type of item:
List<DocumentSnapshot> item = [];
I created an rest api method like below,
Future<bool> activateAccount(int id, int code) async{
final body = {"code": '$code'};
final response = await client.post(
'$baseUrl/user/account/$id',
body: body
);
if(response.statusCode == 200){
return true;
}else return false;
}
but i can't use this method in if statement like this:
bool a = userApiService.activateAccount(...)
if(a){
...
}
because:
A value of type 'Future<bool>' can't be assigned to a variable of type 'bool'.
in what is problem?
how to change this method to return it boolean depending on the result of the operation?
I would like to include in my raisedButton if statement:
child: RaisedButton(
onPressed: () {
userApiService.activateAccount(sharedPreferences.getInt('newUserId'), int.parse(activeCode));
// sharedPreferences.clear();
},
child: Text("ENTER",
style: TextStyle(color: Colors.white, fontSize: 16.0, fontWeight: FontWeight.bold)),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(40.0)),
color: Colors.red,
)
Returning the bool like this shorter than yours;
Future<bool> activateAccount(int id, int code) async {
final body = {"code": '$code'};
final response = await client.post('$baseUrl/user/account/$id', body: body);
return response.statusCode == 200;
}
And to use return value is you need to use await keyword;
bool a = await userApiService.activateAccount(...)
if(a){
...
}
for using with your button just add async keyword before curly brackets;
onPressed: () async {
bool a = await userApiService.activateAccount(...)
if(a){
...
}
}
I have a function that has a callback as shown below, and I want to return account that is returned in the callback as a response to the request for the function. How could I res.send the account (since I cannot return values from a callback function)
#get('/payments/retrieve-stripe/{id}', {
responses: {
'200': {
description: 'User model instance',
content: {'application/json': {schema: {'x-ts-type': User}}},
},
},
})
async retrieveStripe(#param.path.number('id') id: number,
#requestBody() req: any): Promise<any> {
if (!req.stripeAccountId) {
throw new HttpErrors.NotFound('No Stripe Account');
}
else {
stripe.accounts.retrieve(
req.stripeAccountId,
async function(err: any, account: any) {
//console.log(err)
console.log(account)
return account
})
}
}
If you're stuck using a callback any any point in your code you're going to use manual promises (or maybe some promise wrapping library).
Instead of using async and return, use resolve() which functionally can return from any point in your function, regardless of scope.
#get('/payments/retrieve-stripe/{id}', {
responses: {
'200': {
description: 'User model instance',
content: {'application/json': {schema: {'x-ts-type': User}}},
},
},
})
retrieveStripe(#param.path.number('id') id: number, #requestBody() req: any): Promise<any> {
return new Promise((resolve, reject) => {
if (!req.stripeAccountId) {
throw new HttpErrors.NotFound('No Stripe Account');
}
else {
stripe.accounts.retrieve(req.stripeAccountId, function(err: any, account: any) {
resolve(account);
})
}
});
}