firebase - Flutter FutureBuilder 抛出错误,但在模拟时它按预期工作
问题描述
我是 Flutter 的初学者,目前在 Flutter 中正确实现 FutureBuilder 时遇到问题。我正在尝试构建一个用户页面,我的用户信息存储在 Firebase 中,每次访问用户页面时,它都会检索当前用户数据并在页面上显示数据。这是我为实现编写的代码:
class UserPage extends StatefulWidget{
@override
UserPageState createState() => UserPageState();
}
class UserPageState extends State<UserPage>{
String userName;
String userEmail;
String collegeName;
Future _infoInit() async {
userName = await HelperFunctions.getUserNamePreference();
userEmail = await HelperFunctions.getUserEmailPreference();
collegeName = await HelperFunctions.getUserCollegePreference();
}
Widget userScaffold(BuildContext context, AsyncSnapshot snapshot){
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(
userName
),
backgroundColor: Colors.lightBlue,
),
body:Center(
child: Text("This is User Page")
)
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _infoInit(),
builder: (context,AsyncSnapshot snapshot) => userScaffold(context, snapshot)
);
}
}
目前我写的唯一部分是在应用栏上显示当前登录的用户,当我运行代码时,它似乎运行成功。但是,当我查看 android studio 控制台时,我可以看到它实际上面临着错误,我认为这与 FutureBuilder 小部件中执行的异步函数有关。
Error Message:
Performing hot reload...
Syncing files to device iPhone 11...
Reloaded 7 of 650 libraries in 397ms.
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building FutureBuilder<dynamic>(dirty, state: _FutureBuilderState<dynamic>#772f1):
A non-null String must be provided to a Text widget.
'package:flutter/src/widgets/text.dart':
Failed assertion: line 298 pos 10: 'data != null'
The relevant error-causing widget was:
FutureBuilder<dynamic> file:///Users/nossu3751/Downloads/flutter_project/moim_app/lib/user/profile.dart:39:12
When the exception was thrown, this was the stack:
#2 new Text (package:flutter/src/widgets/text.dart:298:10)
#3 UserPageState.userScaffold (package:moimapp/user/profile.dart:26:18)
#4 UserPageState.build.<anonymous closure> (package:moimapp/user/profile.dart:41:54)
#5 _FutureBuilderState.build (package:flutter/src/widgets/async.dart:732:55)
#6 StatefulElement.build (package:flutter/src/widgets/framework.dart:4619:28)
更准确地说,它说我试图在 FutureBuilder 中使用的用户名是 null,即使我相信我已经通过运行 _infoInit() 方法分配了该值,并且它确实在模拟器上正确显示。
如果有人能让我知道我在这里做错了什么,以及我能做些什么来阻止这条消息再次出现,我将非常感激。非常感谢您!
解决方案
builder
问题是每次更改 AsyncSnapshot 时都会调用FutureBuilder 的方法(并且,最初,快照没有数据)。builder
因此,在被调用的前几次,userName
它将为空,从而给您该错误;但是在某个时间点之后,用户名将被获取,当调用构建器函数时,您将在屏幕上正确看到用户名。
使用 FutureBuilder 的惯用方式如下:
FutureBuilder(
future: myFuture,
builder: (context, AsyncSnapshot snapshot) {
// Try adding this print to your code to see when this method is being executed!
print('Building with snapshot = $snapshot');
if (snapshot.hasData) {
// return widget with data - in your case, userScaffold
}
else if (snapshot.hasError) {
// return widget informing of error
}
else {
// still loading, return loading widget (for example, a CircularProgressIndicator)
}
},
);
因此,最初,将使用没有数据的快照调用构建器函数(“else”分支)。在这种情况下,您可能希望显示一个加载小部件。然后,一段时间后,Future 完成,并使用包含数据或某些错误的快照调用构建器函数。
您的代码中的另一件重要事情是您的函数_infoInit
实际上并没有返回任何内容。所以,事实上,你的 FutureBuilder 没有使用来自 AsyncSnapshot 的数据(这意味着上面的代码段实际上不会工作,因为snapshot.hasData
永远不会是真的)。使用 FutureBuilder,您通常希望使用 AsyncSnapshot 返回的数据来构建小部件。相反,您的代码中发生的是:
- FutureBuilder 被创建。这调用
_infoInit()
,触发从 Firebase 获取数据; - 调用 FutureBuilder 的 builder 方法。它尝试使用
userName
,但它为空,因此 Flutter 显示失败的断言; _infoInit()
获取所有数据,并返回一个 Future(由于async
方法签名中的子句,这个未来会自动返回;但是,如果没有return
子句,它实际上不会返回任何数据)。但是尽管 Future 没有任何数据,状态中的 3 个变量(包括userName
)已经更新,现在包含一些数据。- 由于传递给 FutureBuilder 的未来已经完成,所以再次调用 builder 方法。这一次,
userName
有数据,所以它可以正确构建。
像你一样编写代码很好,但在这种情况下,你不需要使用 FutureBuilder。您可以只_infoInit()
从小部件的initState()
方法调用(initState 是第一次构建 State 时调用的方法),并在获取数据后调用 setState()。看起来是这样的:
class UserPage extends StatefulWidget {
@override
UserPageState createState() => UserPageState();
}
class UserPageState extends State<UserPage> {
String userName;
String userEmail;
String collegeName;
bool loadingData = true;
@override
void initState() {
_infoInit();
}
// This can be void now, since we're changing the state, rather than returning a Future
void _infoInit() async {
String userName = await HelperFunctions.getUserNamePreference();
String userEmail = await HelperFunctions.getUserEmailPreference();
String collegeName = await HelperFunctions.getUserCollegePreference();
setState(() {
// In theory, we could have just updated the state variables above, but the
// recommended practice is to update state variables inside setState.
this.userName = userName;
this.userEmail = userEmail;
this.collegeName = collegeName;
loadingData = false;
});
}
Widget userScaffold(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(userName),
backgroundColor: Colors.lightBlue,
),
body: Center(child: Text("This is User Page")));
}
@override
Widget build(BuildContext context) {
if (loadingData) {
// We don't have the data yet, so return a widget to indicate some loading state
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return userScaffold(context);
}
}
上面的代码片段没有处理获取数据时的错误,您可能想要这样做。为此,您可能会使用一个名为“hasError”的标志或其他东西 - 最终,它将提供与“惯用”FutureBuilderbuilder
方法的编写方式非常相似的代码。
两者都是有效的方法;FutureBuilder 可能使用更少的代码(如果您的其余代码已经使用 Futures,则使用起来可能更简单),但最终,这取决于您的偏好。