Flutter asyncMap 直到 setState 才会运行
Posted
技术标签:
【中文标题】Flutter asyncMap 直到 setState 才会运行【英文标题】:Flutter asyncMap not run until setState 【发布时间】:2021-04-16 03:21:46 【问题描述】:我正在制作一个聊天应用程序,它在同一个列表中显示群聊和私人聊天。
我使用 Firestore 作为数据库,并在其中存储用户、组和联系人的数据。我有一个消息屏幕,显示用户使用StreamBuilder
的聊天列表。
我想根据组的数据以不同的方式显示数据。群聊有他们的群组图片、与联系人中的用户进行私人聊天、他们的头像显示,以及带有通用图标显示的私人聊天与用户不在联系人中。
我首先在 DatabaseService 类中遍历流,然后将其放入变量中并将其设置为StreamBuilder
的流。这很好用,但我还想要一个列表来检查用户是否已经与另一个用户进行了私人聊天,而无需从 Firestore 获取数据。
API.dart
//this is where I put my code to connect and read/write data from Firestore
final FirebaseFirestore _db = FirebaseFirestore.instance;
Api();
....
Stream<QuerySnapshot> streamCollectionByArrayAny(
String path, String field, dynamic condition)
return _db
.collection(path)
.where(field, arrayContainsAny: condition)
.snapshots();
DatabaseService.dart
...
List<GroupModel> groups; //List of Groups
Stream<List<GroupModel>> groupStream; //Stream of List Group
...
Stream<QuerySnapshot> fetchGroupsByMemberArrayAsStream(
String field, dynamic condition)
return _api.streamCollectionByArrayAny('groups', field, condition);
//function to get Contact Detail using List of Group User
Future<ContactModel> getContactDetail(List<dynamic> members) async
//remove current user id from the list
members.removeWhere((element) => element.userId == user.userId);
//getContactbyId return a ContactModel object from Firestore
ContactModel contactModel =
await getContactById(user.userId, members.first.userId);
if (contactModel != null && contactModel.userId.isNotEmpty)
return contactModel;
else
return new ContactModel(
userId: members.first.userId, nickname: "", photoUrl: "");
Future<GroupModel> generateGroupMessage(GroupModel group) async
//check if Group Chat or Private chat
if (group.type == 1)
ContactModel contactModel = await getContactDetail(group.membersList);
group.groupName = contactModel.nickname.isNotEmpty
? contactModel.nickname
: contactModel.userId;
group.groupPhoto = contactModel.photoUrl;
print("Add");
//add the group data into List<GroupModel> groups
groups.add(group);
return group;
void refreshMessageList()
groups = [];
print("refresh");
//get Group Data as Stream from FireStore base on the user data in the Member Array of Group then map it to Stream while also change data base on Group type in generateGroupMessage
groupStream = fetchGroupsByMemberArrayAsStream('membersList', [
"isActive": true, "role": 1, "userId": user.userId,
"isActive": true, "role": 2, "userId": user.userId
]).asyncMap((docs) => Future.wait([
for (GroupModel group in docs.docs
.map((doc) => GroupModel.fromMap(doc.data()))
.toList())
generateGroupMessage(group)
]));
Message.dart
@override
void initState()
super.initState();
...
databaseService.refreshMessageList();
setState(() );
@override
Widget build(BuildContext context)
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(horizontal: 16),
margin: EdgeInsets.only(top: 24),
child: Column(
children: [
...
Flexible(
child: StreamBuilder(
stream: databaseService.groupStream,
builder: (context, AsyncSnapshot<List<GroupModel>> snapshot)
if (!snapshot.hasData)
print("No data");
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.grey),
),
);
else
print("Has data");
groups = List.from(snapshot.data);
groups.removeWhere(
(element) => element.recentMessageContent.isEmpty);
groups.sort((group1, group2)
if (DateTime.parse(group1.recentMessageTime)
.isAfter(DateTime.parse(group2.recentMessageTime)))
return -1;
else
return 1;
);
return ListView.builder(
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) =>
buildItem(context, groups[index]),
itemCount: groups.length,
),
),
),
],)));
Widget buildItem(BuildContext context, GroupModel group)
if (group.recentMessageContent == '')
return Container();
else
return Column(
children: [
Container(
child: InkWell(
child: Row(
children: <Widget>[
Material(
child: group.groupPhoto.isNotEmpty
? CachedNetworkImage(
placeholder: (context, url) => Container(
child: CircularProgressIndicator(
strokeWidth: 1.0,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.grey),
),
width: 60.0,
height: 60.0,
padding: EdgeInsets.all(10.0),
),
imageUrl: group.groupPhoto,
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
)
: Icon(
group.type == 1
? Icons.account_circle
: Icons.group,
size: 60.0,
color: Colors.grey,
),
borderRadius: BorderRadius.all(Radius.circular(30.0)),
clipBehavior: Clip.hardEdge,
),
SizedBox(
width: 150,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
group.groupName,
style: TextStyle(
color: colorBlack,
fontSize: 12,
fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
),
Text(
group.recentMessageContent,
style: TextStyle(
color: Colors.grey,
fontSize: 10,
height: 1.6),
overflow: TextOverflow.ellipsis,
),
],
),
margin: EdgeInsets.only(left: 12.0),
),
),
Spacer(),
Text(
formatDateTime(group.recentMessageTime),
style: TextStyle(color: Colors.grey, fontSize: 10),
),
],
),
onTap: ()
switch (group.type)
case 1:
Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(
settings:
RouteSettings(name: "/message/chatPage"),
builder: (context) => ChatPage(group: group)))
.then((value) => setState);
break;
case 2:
Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(
settings:
RouteSettings(name: "/message/chatGroup"),
builder: (context) =>
ChatGroupPage(group: group)))
.then((value) => setState(() ));
break;
),
),
Divider(
color: Colors.grey,
),
],
);
ChatPage
和 ChatGroupPage
分别导航到私人聊天和群聊,用户可以在其中将聊天伙伴或群组成员添加到联系人中。
添加完成后,我调用databaseService.refreshMessageList
来刷新列表组的流,因此当我导航回消息屏幕时,它将相应地刷新并显示。但是,List<GroupModel>
组变为空白,并且在我导航回消息屏幕之前不会添加数据。
我调试了应用程序,发现列表变为空白,因为它执行 groups = [] 但没有运行 .asyncMap
,直到我热重载或导航消息屏幕并将 setState
放入 .then 以刷新数据.
添加到联系人时,我需要列表组来检查 2 个用户是否已经进行了私人聊天以创建新的。我已经尝试将setState
放在databaseService.refreshMessageList
之后,但还是不行。
谁能帮助我并提供解决方案?我知道这不是一个好问题,但我已经被这个问题困扰了将近一个星期,迫切需要一个答案。提前谢谢你。
编辑
这是我的数据结构:
用户
/users (collection)
/userId
/user (document)
- userId
- nickname
- photoUrl
- token
- /contacts (subcollection)
/contactId
/contact (document)
- userId
- nickname
- photoUrl
组:
/groups (collection)
/groupId
/group (document)
- groupId
- groupName
- type
- membersList (List<Map<String, dynamic>>)
- member: userId, isActive, role
- recentMessageContent
- recentMessageTime
- recentMessageType
消息:
/messages (collection)
/groupId
/groupMessage (document)
/messages (subcollection)
/messageId
/message (document)
- messageContent
- messageTime
- messageType
【问题讨论】:
您能否也将您的数据结构添加到问题中?这将有助于了解您的配置。 当然,我已经在问题中添加了它。如果需要,请查看。 【参考方案1】:您可以使用array membership,例如,array-contains
方法可以在不执行任何操作的情况下查询数组中的元素。有一个有趣的article 提供了一些您可能感兴趣的示例。
另一种选择是迭代两个数组,直到匹配您需要的值。但是,如果没有正确实现,迭代可能会导致performance issues。
【讨论】:
感谢您的回答。但是,我不确定这将如何解决我的问题。我的问题似乎是.asyncMap
在 setState 重新加载或手动热重新加载之前不会运行,但在我退出 ChatPage
到 Messages
屏幕之前它不会运行。 fetchGroupsByMemberArrayAsStream
已经使用 array-contains
将数据作为 QuerySnapshots 流回,因为我的 membersList 数组中包含地图,我提供了完整的地图数据,例如"isActive": true, "role": 2, "userId": user.userId
用于搜索,所以使用它不是问题
我在您提供的fetchGroupsByMemberArrayAsStream
代码sn-p 中看不到array-contains
。你有没有设法让它工作?看看问题出在哪里会很有趣。
你可以在Debugging Flutter Apps helpful找到这个链接
我没有,但我能够通过将.asyncMap
中的函数拆分为另一个函数并将addGroup
放在那里以填充group
数组,从而将数据添加到group
数组中.当我运行调试器并在 ChatPage 中执行 refreshMessageList
函数时,我将断点放在所有函数行上,并看到它执行 fetchGroupsByMemberArrayAsStream
但之后不执行 .asyncMap 并且只有当我导航回 @ 987654340@ 我将 setState 放入 .then
的位置,它是否继续运行 .asyncMap。
我将更新代码以包含 fetchGroupsByMemberArrayAsStream
确切代码以上是关于Flutter asyncMap 直到 setState 才会运行的主要内容,如果未能解决你的问题,请参考以下文章
Flutter - 在 GridView 中禁用滚动,直到孩子填满高度
在构建异常期间调用的 setState() 或 markNeedsBuild() 阻止我执行回调
如何在flutter中实现日历功能,以便在DateTimePickerFormField中显示当前日期之后的日期,直到2天之后