「posts」というFirestoreコレクションから投稿を取得しようとしています。これには、投稿作成者のuserIDと投稿の説明が含まれます。これは、両方を使用して可能です。 StreamBuilderおよびFutureBuilder(スナップショットは1回しか取得されず、フィールドが変更されても更新されないため、推奨されません)。
ただし、投稿作成者のuserIDを使用して「users」という別のコレクションをクエリし、userIdと一致するドキュメントを取得したいと思います。
これが私の最初のアプローチでした:
StreamBuilder<QuerySnapshot>(
stream:Firestore.instance.collection("posts").snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: _showProgressBar,
);
}
List<DocumentSnapshot> reversedDocuments = snapshot.data.documents.reversed.toList();
return ListView.builder(
itemCount: reversedDocuments.length,
itemBuilder: (BuildContext context, int index){
String postAuthorID = reversedDocuments[index].data["postAuthorID"].toString();
String postAuthorName = '';
Firestore.instance.collection("users")
.where("userID", isEqualTo: postAuthorID).snapshots().listen((dataSnapshot) {
print(postAuthorName = dataSnapshot.documents[0].data["username"]);
setState(() {
postAuthorName = dataSnapshot.documents[0].data["username"];
});
});
String desc = reversedDocuments[index].data["post_desc"].toString();
return new ListTile(
title: Container(
child: Row(
children: <Widget>[
Expanded(
child: Card(
child: new Column(
children: <Widget>[
ListTile(
title: Text(postAuthorName, //Here, the value is not changed, it holds empty space.
style: TextStyle(
fontSize: 20.0,
),
),
subtitle: Text(desc),
),
)
ListView.builder()はDocumentSnapshotリストに基づいてのみアイテムをレンダリングでき、ビルダー内のクエリを処理できないことを理解した後。
多くの調査の後:私はinitState()でリストを作成しようとする、Nested Stream Builderを使用しようとするなど、多くの代替手段を試しました:
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('posts').snapshots(),
builder: (context, snapshot1){
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("users").snapshots(),
builder: (context, snapshot2){
return ListView.builder(
itemCount: snapshot1.data.documents.length,
itemBuilder: (context, index){
String desc = snapshot1.data.documents[index].data['post_description'].toString();
String taskAuthorID = snapshot1.data.documents[index].data['post_authorID'].toString();
var usersMap = snapshot2.data.documents.asMap();
String authorName;
username.forEach((len, snap){
print("Position: $len, Data: ${snap.data["username"]}");
if(snap.documentID == post_AuthorID){
authorName = snap.data["username"].toString();
}
});
return ListTile(
title: Text(desc),
subtitle: Text(authorName), //Crashes here...
);
},
);
}
);
}
);
ストリームグループを試してみましたが、2つのストリームを組み合わせるだけなので、これを実行する方法を理解できませんでしたが、2番目のストリームを最初のストリームからの値でフェッチする必要があります。
これは私のFirebaseコレクションのスクリーンショットです。
これは非常に簡単なことですが、それを達成するためのチュートリアルや記事はまだ見つかりませんでした。
私は同様の質問を投稿し、後で解決策を見つけました:itemBuilderによって返されたウィジェットをステートフルにし、その中でFutureBuilderを使用します。
StreamBuilder内のすべてのDocumentSnapshotの追加クエリ
これが私のコードです。あなたの場合、ListTileの代わりに新しいStatefulウィジェットを使用したいので、FutureBuilderを追加して非同期関数を呼び出すことができます。
StreamBuilder(
stream: Firestore.instance
.collection("messages").snapshots(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: PlatformProgressIndicator(),
);
default:
return ListView.builder(
reverse: true,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
List rev = snapshot.data.documents.reversed.toList();
ChatMessageModel message = ChatMessageModel.fromSnapshot(rev[index]);
return ChatMessage(message);
},
);
}
},
)
class ChatMessage extends StatefulWidget {
final ChatMessageModel _message;
ChatMessage(this._message);
@override
_ChatMessageState createState() => _ChatMessageState(_message);
}
class _ChatMessageState extends State<ChatMessage> {
final ChatMessageModel _message;
_ChatMessageState(this._message);
Future<ChatMessageModel> _load() async {
await _message.loadUser();
return _message;
}
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: FutureBuilder(
future: _load(),
builder: (context, AsyncSnapshot<ChatMessageModel>message) {
if (!message.hasData)
return Container();
return Row(
children: <Widget>[
Container(
margin: const EdgeInsets.only(right: 16.0),
child: GestureDetector(
child: CircleAvatar(
backgroundImage: NetworkImage(message.data.user.pictureUrl),
),
onTap: () {
Navigator.of(context)
.Push(MaterialPageRoute(builder: (context) =>
ProfileScreen(message.data.user)));
},
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
message.data.user.name,
style: Theme.of(context).textTheme.subhead,
),
Container(
margin: const EdgeInsets.only(top: 5.0),
child: _message.mediaUrl != null
? Image.network(_message.mediaUrl, width: 250.0)
: Text(_message.text))
],
),
)
],
);
},
),
);
}
}
class ChatMessageModel {
String id;
String userId;
String text;
String mediaUrl;
int createdAt;
String replyId;
UserModel user;
ChatMessageModel({String text, String mediaUrl, String userId}) {
this.text = text;
this.mediaUrl = mediaUrl;
this.userId = userId;
}
ChatMessageModel.fromSnapshot(DocumentSnapshot snapshot) {
this.id = snapshot.documentID;
this.text = snapshot.data["text"];
this.mediaUrl = snapshot.data["mediaUrl"];
this.createdAt = snapshot.data["createdAt"];
this.replyId = snapshot.data["replyId"];
this.userId = snapshot.data["userId"];
}
Map toMap() {
Map<String, dynamic> map = {
"text": this.text,
"mediaUrl": this.mediaUrl,
"userId": this.userId,
"createdAt": this.createdAt
};
return map;
}
Future<void> loadUser() async {
DocumentSnapshot ds = await Firestore.instance
.collection("users").document(this.userId).get();
if (ds != null)
this.user = UserModel.fromSnapshot(ds);
}
}
私がこれを理解するために数時間費やしたので、将来のために投稿する-それが他の誰かを救うことを願っています。
まず、Stream
sを読むことをお勧めします: https://www.dartlang.org/tutorials/language/streams これは少し助けになるでしょう読んだ
自然な考えは、ネストされたStreamBuilder
を外側のStreamBuilder
内にネストすることです。これは、内側のListView
がデータを受け取った結果、StreamBuilder
のサイズが変化しない場合は問題ありません。データがない場合は固定サイズのコンテナーを作成し、準備ができたらデータリッチなウィジェットをレンダリングできます。私の場合、 "outer"コレクションと "inner"コレクションの両方のドキュメントごとにCard
を作成したいと思いました。たとえば、グループコレクションがあり、各グループにユーザーがいます。私はこのようなビューを望んでいました:
[
Group_A header card,
Group_A's User_1 card,
Group_A's User_2 card,
Group_B header card,
Group_B's User_1 card,
Group_B's User_2 card,
]
ネストされたStreamBuilder
アプローチはデータをレンダリングしましたが、ListView.builder
が問題でした。スクロールすると、高さが(group_header_card_height
+ inner_listview_no_data_height
)。内部のListView
がデータを受信すると、リストの高さが拡大されてスクロールジャークが収まりました。それは受け入れられないUXです。
ソリューションの要点:
StreamBuilder
のbuilder
を実行する前に、すべてのデータを取得する必要があります。つまり、Stream
には両方のコレクションのデータを含める必要がありますStream
は複数のアイテムを保持できますが、Stream<List<MyCompoundObject>>
。この回答へのコメント( https://stackoverflow.com/a/53903960/608347 )が役立ちました私が取ったアプローチは基本的に
グループとuserListのペアのストリームを作成する
a。グループのクエリ
b。グループごとに、適切なuserListを取得します
c。各ペアをラップするカスタムオブジェクトのリストを返します
StreamBuilder
は通常どおりですが、QuerySnapshot
sではなくgroup-to-userListオブジェクト上
それがどのように見えるか
複合ヘルパーオブジェクト:
class GroupWithUsers {
final Group group;
final List<User> users;
GroupWithUsers(this.group, this.users);
}
StreamBuilder
Stream<List<GroupWithUsers>> stream = Firestore.instance
.collection(GROUP_COLLECTION_NAME)
.orderBy('createdAt', descending: true)
.snapshots()
.asyncMap((QuerySnapshot groupSnap) => groupsToPairs(groupSnap));
return StreamBuilder(
stream: stream,
builder: (BuildContext c, AsyncSnapshot<List<GroupWithUsers>> snapshot) {
// build whatever
});
基本的に、「グループごとにペアを作成」して、型のすべての変換を処理します
Future<List<GroupWithUsers>> groupsToPairs(QuerySnapshot groupSnap) {
return Future.wait(groupSnap.documents.map((DocumentSnapshot groupDoc) async {
return await groupToPair(groupDoc);
}).toList());
}
最後に、User
sを取得してヘルパーを作成するための実際の内部クエリ
Future<GroupWithUsers> groupToPair(DocumentSnapshot groupDoc) {
return Firestore.instance
.collection(USER_COLLECTION_NAME)
.where('groupId', isEqualTo: groupDoc.documentID)
.orderBy('createdAt', descending: false)
.getDocuments()
.then((usersSnap) {
List<User> users = [];
for (var doc in usersSnap.documents) {
users.add(User.from(doc));
}
return GroupWithUser(Group.from(groupDoc), users);
});
}