StreamBuilder ListView returns 首次加载时来自 Firestore 的空列表
StreamBuilder ListView returns empty list from Firestore on first load
在我正在构建的应用程序中,作为注册过程的一部分,为每个 'User' 文档创建了一个子集合,最多包含 100 个文档。
我试图在 StreamBuilder
中显示这些子集合文档。
我有一个奇怪的错误,我无法解决。当用户第一次查看数据时,StreamBuilder
不显示数据。相反,它 returns 一个空列表。
我可以看到子集合中的文档已正确生成。数据设置在带有 StreamBuilder 的页面之前的页面上。即使有延迟,我也认为新文档会刚刚开始出现在 StreamBuilder
内。
Firebase console view
StreamBuilder 确实 如果应用程序重新启动 - 或者如果用户注销并再次登录,则按预期显示数据。
下面是我使用的代码:
Stream<QuerySnapshot> provideActivityStream() {
return Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
}
...
Widget activityStream() {
return Container(
padding: const EdgeInsets.all(20.0),
child: StreamBuilder<QuerySnapshot>(
stream: provideActivityStream(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
if(snapshot.data == null) {
return CircularProgressIndicator();
}
if(snapshot.data.documents.length < 1) {
return new Text(
snapshot.data.documents.toString()
);
}
if (snapshot != null) {
print('$currentUser.userId');
}
if (
snapshot.hasData && snapshot.data.documents.length > 0
) {
print("I have documents");
return new ListView(
children: snapshot.data.documents.map((
DocumentSnapshot document) {
return new PointCard(
title: document['title'],
type: document['type'],
);
}).toList(),
);
}
}
)
);
}
编辑:根据评论请求添加主构建
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text("Home"),
actions: <Widget>[
],
bottom: TabBar(
tabs: [
Text("Account"),
Text("Activity"),
Text("Links"),
],
),
),
body: TabBarView(
children: [
accountStream(),
activityStream(),
linksStream()
]
)
),
);
}
}
我尝试解决
我最初认为这是一个连接错误,所以根据 switch (snapshot.connectionState)
创建了一系列案例。我可以看到 ConnectionState.active = true
所以我认为在 Firestore 中添加一个新文档可能会有效果但什么也没做。
我尝试了以下方法来使初始流构造函数异步。它无法加载任何数据。
Stream<QuerySnapshot> provideActivityStream() async* {
await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
}
我试过删除 tabcontroller 元素 - 例如只有一个页面 - 但这也无济于事。
我已经尝试使用 DocumentSnapshot
和 QuerySnapshot
访问数据。我两个都有问题。
我相信这很简单,但坚持下去。非常感谢任何帮助。谢谢!
它不是使用其中任何一个查询快照和文档快照提取的
您应该首先使用Querysnapshot 进行查询,然后将信息检索到Documentsnapshot
是的,加载文档可能需要几秒钟解决方案是正确的,您应该使用 async 和 await 函数
我建议您使用直接快照而不是 streamBuilder
我们可以在statefullWidget的initstate中加载文档快照
当您的 class 是一个 statefullWidget 并且问题也与 state
有关时有效
...
bool isLoading;
List<DocumentSnapshot> activity =[];
QuerySnapshot user;
@override
void initState() {
print("in init state");
super.initState();
getDocument();
`` }
getDocument() async{
setState(() {
isLoading = true;
});
user= await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.getDocuments();
activity.isEmpty ? activity.addAll(user.documents) : null;
setState(() {
isLoading = false;
});
}
//inside Widget build(BuildContext context) { return Scaffold( in your body
//section of scaffold in the cointainer
Container(
padding: const EdgeInsets.all(20.0),
child: isLoading ?
CircularProgressIndicator(),
:ListView.builder(
itemCount: global.category.length,
itemBuilder: (context, index) {
return PointCard(
title: activity[index].data['title'],
type: activity[index].data['type'],
);//pointcard
}
),//builder
),//container
我们还可以尝试以下
QuerySnapshot qs;
Stream<QuerySnapshot> provideActivityStream() async{
qs= await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
return qs;
}//this should work
但是根据 streambuilder 的基础知识,如果上面的部分不起作用
然后还有一个
QuerySnapshot qs;
Stream<QuerySnapshot> provideActivityStream() async* {
qs= await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
yield qs;
}//give this a try
tl;博士
- 需要使用
setState
让 Firebase currentUser uid
可用于小部件
- 需要使用
AutomaticKeepAliveClientMixin
才能与 TabBar
一起正常工作
- 我认为使用 Provider 包可能是一种更好的持久化用户状态的方法,但并没有解决这个问题
说明
我的代码获取带有 Future 的 currentUser uid
。根据 ,这是一个问题,因为小部件将在 FirebaseAuth 返回 uid
之前全部构建。我最初尝试使用 initState
来获取 uid
但它具有完全相同的同步问题。从函数调用 setState
以调用 FirebaseAuth.instance
允许小部件树更新。
我将此小部件放置在 TabBar 小部件中。我的理解是,每次从视图中删除选项卡时,它都会被处理掉,因此在返回时会重建。这导致了进一步的状态问题。 API AutomaticKeepAlive mixin 的文档是 here
解决方案代码
添加评论,希望对其他人的理解有所帮助(或者有人可以纠正我的误解)
activitylist.dart
class ActivityList extends StatefulWidget {
// Need a stateful widget to use initState and setState later
@override
_ActivityListState createState() => _ActivityListState();
}
class _ActivityListState extends State<ActivityList>
with AutomaticKeepAliveClientMixin<ActivityList>{
// `with AutomaticKeepAliveClientMixin` added for TabBar state issues
@override
bool get wantKeepAlive => true;
// above override required for mixin
final databaseReference = Firestore.instance;
@override
initState() {
this.getCurrentUser(); // call the void getCurrentUser function
super.initState();
}
FirebaseUser currentUser;
void getCurrentUser() async {
currentUser = await FirebaseAuth.instance.currentUser();
setState(() {
currentUser.uid;
});
// calling setState allows widgets to access uid and access stream
}
Stream<QuerySnapshot> provideActivityStream() async* {
yield* Firestore.instance
.collection("users")
.document(currentUser.uid)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Container(
padding: const EdgeInsets.all(20.0),
child: StreamBuilder<QuerySnapshot>(
stream: provideActivityStream(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if(snapshot.hasError) return CircularProgressIndicator();
if(snapshot.data == null) return CircularProgressIndicator();
else if(snapshot.data !=null) {
return new ListView(
children: snapshot.data.documents.map((
DocumentSnapshot document) {
return new ActivityCard(
title: document['title'],
type: document['type'],
startDateLocal: document['startDateLocal'],
);
}).toList(),
);
}
},
)
);
}
}
home.dart
...
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text("Home"),
actions: <Widget>[
],
bottom: TabBar(
tabs: [
Text("Account"),
Text("Activity"),
Text("Links"),
],
),
),
body: TabBarView(
children: [
accountStream(),
ActivityList(), // now calling a stateful widget in an external file
linksStream()
]
)
),
);
}
}
在我正在构建的应用程序中,作为注册过程的一部分,为每个 'User' 文档创建了一个子集合,最多包含 100 个文档。
我试图在 StreamBuilder
中显示这些子集合文档。
我有一个奇怪的错误,我无法解决。当用户第一次查看数据时,StreamBuilder
不显示数据。相反,它 returns 一个空列表。
我可以看到子集合中的文档已正确生成。数据设置在带有 StreamBuilder 的页面之前的页面上。即使有延迟,我也认为新文档会刚刚开始出现在 StreamBuilder
内。
Firebase console view
StreamBuilder 确实 如果应用程序重新启动 - 或者如果用户注销并再次登录,则按预期显示数据。
下面是我使用的代码:
Stream<QuerySnapshot> provideActivityStream() {
return Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
}
...
Widget activityStream() {
return Container(
padding: const EdgeInsets.all(20.0),
child: StreamBuilder<QuerySnapshot>(
stream: provideActivityStream(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
if(snapshot.data == null) {
return CircularProgressIndicator();
}
if(snapshot.data.documents.length < 1) {
return new Text(
snapshot.data.documents.toString()
);
}
if (snapshot != null) {
print('$currentUser.userId');
}
if (
snapshot.hasData && snapshot.data.documents.length > 0
) {
print("I have documents");
return new ListView(
children: snapshot.data.documents.map((
DocumentSnapshot document) {
return new PointCard(
title: document['title'],
type: document['type'],
);
}).toList(),
);
}
}
)
);
}
编辑:根据评论请求添加主构建
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text("Home"),
actions: <Widget>[
],
bottom: TabBar(
tabs: [
Text("Account"),
Text("Activity"),
Text("Links"),
],
),
),
body: TabBarView(
children: [
accountStream(),
activityStream(),
linksStream()
]
)
),
);
}
}
我尝试解决
我最初认为这是一个连接错误,所以根据 switch (snapshot.connectionState)
创建了一系列案例。我可以看到 ConnectionState.active = true
所以我认为在 Firestore 中添加一个新文档可能会有效果但什么也没做。
我尝试了以下方法来使初始流构造函数异步。它无法加载任何数据。
Stream<QuerySnapshot> provideActivityStream() async* {
await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
}
我试过删除 tabcontroller 元素 - 例如只有一个页面 - 但这也无济于事。
我已经尝试使用 DocumentSnapshot
和 QuerySnapshot
访问数据。我两个都有问题。
我相信这很简单,但坚持下去。非常感谢任何帮助。谢谢!
它不是使用其中任何一个查询快照和文档快照提取的
您应该首先使用Querysnapshot 进行查询,然后将信息检索到Documentsnapshot 是的,加载文档可能需要几秒钟解决方案是正确的,您应该使用 async 和 await 函数
我建议您使用直接快照而不是 streamBuilder
我们可以在statefullWidget的initstate中加载文档快照 当您的 class 是一个 statefullWidget 并且问题也与 state
有关时有效...
bool isLoading;
List<DocumentSnapshot> activity =[];
QuerySnapshot user;
@override
void initState() {
print("in init state");
super.initState();
getDocument();
`` }
getDocument() async{
setState(() {
isLoading = true;
});
user= await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.getDocuments();
activity.isEmpty ? activity.addAll(user.documents) : null;
setState(() {
isLoading = false;
});
}
//inside Widget build(BuildContext context) { return Scaffold( in your body
//section of scaffold in the cointainer
Container(
padding: const EdgeInsets.all(20.0),
child: isLoading ?
CircularProgressIndicator(),
:ListView.builder(
itemCount: global.category.length,
itemBuilder: (context, index) {
return PointCard(
title: activity[index].data['title'],
type: activity[index].data['type'],
);//pointcard
}
),//builder
),//container
我们还可以尝试以下
QuerySnapshot qs;
Stream<QuerySnapshot> provideActivityStream() async{
qs= await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
return qs;
}//this should work
但是根据 streambuilder 的基础知识,如果上面的部分不起作用 然后还有一个
QuerySnapshot qs;
Stream<QuerySnapshot> provideActivityStream() async* {
qs= await Firestore.instance
.collection("users")
.document(widget.userId)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
yield qs;
}//give this a try
tl;博士
- 需要使用
setState
让 Firebase currentUseruid
可用于小部件 - 需要使用
AutomaticKeepAliveClientMixin
才能与TabBar
一起正常工作
- 我认为使用 Provider 包可能是一种更好的持久化用户状态的方法,但并没有解决这个问题
说明
我的代码获取带有 Future 的 currentUser uid
。根据 uid
之前全部构建。我最初尝试使用 initState
来获取 uid
但它具有完全相同的同步问题。从函数调用 setState
以调用 FirebaseAuth.instance
允许小部件树更新。
我将此小部件放置在 TabBar 小部件中。我的理解是,每次从视图中删除选项卡时,它都会被处理掉,因此在返回时会重建。这导致了进一步的状态问题。 API AutomaticKeepAlive mixin 的文档是 here
解决方案代码
添加评论,希望对其他人的理解有所帮助(或者有人可以纠正我的误解)
activitylist.dart
class ActivityList extends StatefulWidget {
// Need a stateful widget to use initState and setState later
@override
_ActivityListState createState() => _ActivityListState();
}
class _ActivityListState extends State<ActivityList>
with AutomaticKeepAliveClientMixin<ActivityList>{
// `with AutomaticKeepAliveClientMixin` added for TabBar state issues
@override
bool get wantKeepAlive => true;
// above override required for mixin
final databaseReference = Firestore.instance;
@override
initState() {
this.getCurrentUser(); // call the void getCurrentUser function
super.initState();
}
FirebaseUser currentUser;
void getCurrentUser() async {
currentUser = await FirebaseAuth.instance.currentUser();
setState(() {
currentUser.uid;
});
// calling setState allows widgets to access uid and access stream
}
Stream<QuerySnapshot> provideActivityStream() async* {
yield* Firestore.instance
.collection("users")
.document(currentUser.uid)
.collection('activities')
.orderBy('startDate', descending: true)
.snapshots();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Container(
padding: const EdgeInsets.all(20.0),
child: StreamBuilder<QuerySnapshot>(
stream: provideActivityStream(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if(snapshot.hasError) return CircularProgressIndicator();
if(snapshot.data == null) return CircularProgressIndicator();
else if(snapshot.data !=null) {
return new ListView(
children: snapshot.data.documents.map((
DocumentSnapshot document) {
return new ActivityCard(
title: document['title'],
type: document['type'],
startDateLocal: document['startDateLocal'],
);
}).toList(),
);
}
},
)
);
}
}
home.dart
...
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text("Home"),
actions: <Widget>[
],
bottom: TabBar(
tabs: [
Text("Account"),
Text("Activity"),
Text("Links"),
],
),
),
body: TabBarView(
children: [
accountStream(),
ActivityList(), // now calling a stateful widget in an external file
linksStream()
]
)
),
);
}
}