Fra*_*len 21 firebase flutter google-cloud-firestore
我在 Flutter 中有一个使用 Firestore 的聊天应用程序,我有两个主要集合:
chats,这是对键自动标识,并且有message,timestamp和uid领域。users,它被键入uid,并且有一个name字段在我的应用程序中,我messages使用这个小部件显示了一个消息列表(来自集合):
class ChatList extends StatelessWidget {
@override
Widget build(BuildContext context) {
var messagesSnapshot = Firestore.instance.collection("chat").orderBy("timestamp", descending: true).snapshots();
var streamBuilder = StreamBuilder<QuerySnapshot>(
stream: messagesSnapshot,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> querySnapshot) {
if (querySnapshot.hasError)
return new Text('Error: ${querySnapshot.error}');
switch (querySnapshot.connectionState) {
case ConnectionState.waiting: return new Text("Loading...");
default:
return new ListView(
children: querySnapshot.data.documents.map((DocumentSnapshot doc) {
return new ListTile(
title: new Text(doc['message']),
subtitle: new Text(DateTime.fromMillisecondsSinceEpoch(doc['timestamp']).toString()),
);
}).toList()
);
}
}
);
return streamBuilder;
}
}
Run Code Online (Sandbox Code Playgroud)
但现在我想显示users每条消息的用户名(来自集合)。
我通常称其为客户端连接,尽管我不确定 Flutter 是否有一个特定的名称。
我找到了一种方法来做到这一点(我已经在下面发布了),但想知道是否有另一种/更好/更惯用的方法来在 Flutter 中进行这种类型的操作。
那么:Flutter 中查找上述结构中每条消息的用户名的惯用方法是什么?
Cen*_*MUR 13
你可以像那样用 RxDart 做到这一点.. https://pub.dev/packages/rxdart
import 'package:rxdart/rxdart.dart';
class Messages {
final String messages;
final DateTime timestamp;
final String uid;
final DocumentReference reference;
Messages.fromMap(Map<String, dynamic> map, {this.reference})
: messages = map['messages'],
timestamp = (map['timestamp'] as Timestamp)?.toDate(),
uid = map['uid'];
Messages.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
@override
String toString() {
return 'Messages{messages: $messages, timestamp: $timestamp, uid: $uid, reference: $reference}';
}
}
class Users {
final String name;
final DocumentReference reference;
Users.fromMap(Map<String, dynamic> map, {this.reference})
: name = map['name'];
Users.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
@override
String toString() {
return 'Users{name: $name, reference: $reference}';
}
}
class CombineStream {
final Messages messages;
final Users users;
CombineStream(this.messages, this.users);
}
Stream<List<CombineStream>> _combineStream;
@override
void initState() {
super.initState();
_combineStream = Observable(Firestore.instance
.collection('chat')
.orderBy("timestamp", descending: true)
.snapshots())
.map((convert) {
return convert.documents.map((f) {
Stream<Messages> messages = Observable.just(f)
.map<Messages>((document) => Messages.fromSnapshot(document));
Stream<Users> user = Firestore.instance
.collection("users")
.document(f.data['uid'])
.snapshots()
.map<Users>((document) => Users.fromSnapshot(document));
return Observable.combineLatest2(
messages, user, (messages, user) => CombineStream(messages, user));
});
}).switchMap((observables) {
return observables.length > 0
? Observable.combineLatestList(observables)
: Observable.just([]);
})
}
Run Code Online (Sandbox Code Playgroud)
对于 rxdart 0.23.x
@override
void initState() {
super.initState();
_combineStream = Firestore.instance
.collection('chat')
.orderBy("timestamp", descending: true)
.snapshots()
.map((convert) {
return convert.documents.map((f) {
Stream<Messages> messages = Stream.value(f)
.map<Messages>((document) => Messages.fromSnapshot(document));
Stream<Users> user = Firestore.instance
.collection("users")
.document(f.data['uid'])
.snapshots()
.map<Users>((document) => Users.fromSnapshot(document));
return Rx.combineLatest2(
messages, user, (messages, user) => CombineStream(messages, user));
});
}).switchMap((observables) {
return observables.length > 0
? Rx.combineLatestList(observables)
: Stream.value([]);
})
}
Run Code Online (Sandbox Code Playgroud)
我得到了另一个版本,这似乎比我对两个嵌套构建器的回答略好。
在这里,我隔离了自定义方法中的数据加载,使用专用Message类来保存消息Document和可选的关联 user 中的信息Document。
class Message {
final message;
final timestamp;
final uid;
final user;
const Message(this.message, this.timestamp, this.uid, this.user);
}
class ChatList extends StatelessWidget {
Stream<List<Message>> getData() async* {
var messagesStream = Firestore.instance.collection("chat").orderBy("timestamp", descending: true).snapshots();
var messages = List<Message>();
await for (var messagesSnapshot in messagesStream) {
for (var messageDoc in messagesSnapshot.documents) {
var message;
if (messageDoc["uid"] != null) {
var userSnapshot = await Firestore.instance.collection("users").document(messageDoc["uid"]).get();
message = Message(messageDoc["message"], messageDoc["timestamp"], messageDoc["uid"], userSnapshot["name"]);
}
else {
message = Message(messageDoc["message"], messageDoc["timestamp"], "", "");
}
messages.add(message);
}
yield messages;
}
}
@override
Widget build(BuildContext context) {
var streamBuilder = StreamBuilder<List<Message>>(
stream: getData(),
builder: (BuildContext context, AsyncSnapshot<List<Message>> messagesSnapshot) {
if (messagesSnapshot.hasError)
return new Text('Error: ${messagesSnapshot.error}');
switch (messagesSnapshot.connectionState) {
case ConnectionState.waiting: return new Text("Loading...");
default:
return new ListView(
children: messagesSnapshot.data.map((Message msg) {
return new ListTile(
title: new Text(msg.message),
subtitle: new Text(DateTime.fromMillisecondsSinceEpoch(msg.timestamp).toString()
+"\n"+(msg.user ?? msg.uid)),
);
}).toList()
);
}
}
);
return streamBuilder;
}
}
Run Code Online (Sandbox Code Playgroud)
与嵌套构建器的解决方案相比,此代码更具可读性,主要是因为数据处理和 UI 构建器更好地分离。它还仅为已发布消息的用户加载用户文档。不幸的是,如果用户发布了多条消息,它将为每条消息加载文档。我可以添加一个缓存,但认为这段代码对于它完成的功能来说已经有点长了。
如果我正确地阅读了这篇文章,那么问题抽象为:如何转换需要进行异步调用来修改流中数据的数据流?
在问题的上下文中,数据流是消息列表,异步调用是获取用户数据并使用流中的该数据更新消息。
可以使用该asyncMap()函数直接在 Dart 流对象中执行此操作。下面是一些演示如何执行此操作的纯 Dart 代码:
import 'dart:async';
import 'dart:math' show Random;
final random = Random();
const messageList = [
{
'message': 'Message 1',
'timestamp': 1,
'uid': 1,
},
{
'message': 'Message 2',
'timestamp': 2,
'uid': 2,
},
{
'message': 'Message 3',
'timestamp': 3,
'uid': 2,
},
];
const userList = {
1: 'User 1',
2: 'User 2',
3: 'User 3',
};
class Message {
final String message;
final int timestamp;
final int uid;
final String user;
const Message(this.message, this.timestamp, this.uid, this.user);
@override
String toString() => '$user => $message';
}
// Mimic a stream of a list of messages
Stream<List<Map<String, dynamic>>> getServerMessagesMock() async* {
yield messageList;
while (true) {
await Future.delayed(Duration(seconds: random.nextInt(3) + 1));
yield messageList;
}
}
// Mimic asynchronously fetching a user
Future<String> userMock(int uid) => userList.containsKey(uid)
? Future.delayed(
Duration(milliseconds: 100 + random.nextInt(100)),
() => userList[uid],
)
: Future.value(null);
// Transform the contents of a stream asynchronously
Stream<List<Message>> getMessagesStream() => getServerMessagesMock()
.asyncMap<List<Message>>((messageList) => Future.wait(
messageList.map<Future<Message>>(
(m) async => Message(
m['message'],
m['timestamp'],
m['uid'],
await userMock(m['uid']),
),
),
));
void main() async {
print('Streams with async transforms test');
await for (var messages in getMessagesStream()) {
messages.forEach(print);
}
}
Run Code Online (Sandbox Code Playgroud)
大多数代码将来自 Firebase 的数据模仿为消息映射流,以及用于获取用户数据的异步函数。这里重要的函数是getMessagesStream().
该代码稍微复杂一些,因为它是流中传入的消息列表。为了防止同步发生获取用户数据的调用,代码使用 aFuture.wait()来收集 a并在所有 Future 完成时List<Future<Message>>创建 a 。List<Message>
在 Flutter 的上下文中,您可以使用来自getMessagesStream()a 的流FutureBuilder来显示 Message 对象。
我工作的第一个解决方案是嵌套两个StreamBuilder实例,每个集合/查询一个实例。
class ChatList extends StatelessWidget {
@override
Widget build(BuildContext context) {
var messagesSnapshot = Firestore.instance.collection("chat").orderBy("timestamp", descending: true).snapshots();
var usersSnapshot = Firestore.instance.collection("users").snapshots();
var streamBuilder = StreamBuilder<QuerySnapshot>(
stream: messagesSnapshot,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> messagesSnapshot) {
return StreamBuilder(
stream: usersSnapshot,
builder: (context, usersSnapshot) {
if (messagesSnapshot.hasError || usersSnapshot.hasError || !usersSnapshot.hasData)
return new Text('Error: ${messagesSnapshot.error}, ${usersSnapshot.error}');
switch (messagesSnapshot.connectionState) {
case ConnectionState.waiting: return new Text("Loading...");
default:
return new ListView(
children: messagesSnapshot.data.documents.map((DocumentSnapshot doc) {
var user = "";
if (doc['uid'] != null && usersSnapshot.data != null) {
user = doc['uid'];
print('Looking for user $user');
user = usersSnapshot.data.documents.firstWhere((userDoc) => userDoc.documentID == user).data["name"];
}
return new ListTile(
title: new Text(doc['message']),
subtitle: new Text(DateTime.fromMillisecondsSinceEpoch(doc['timestamp']).toString()
+"\n"+user),
);
}).toList()
);
}
});
}
);
return streamBuilder;
}
}
Run Code Online (Sandbox Code Playgroud)
正如我的问题中所述,我知道这个解决方案不是很好,但至少它有效。
我看到的一些问题:
如果您知道更好的解决方案,请发布作为答案。
| 归档时间: |
|
| 查看次数: |
12264 次 |
| 最近记录: |