首页 > 解决方案 > 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() 方法分配了该值,并且它确实在模拟器上正确显示。

如果有人能让我知道我在这里做错了什么,以及我能做些什么来阻止这条消息再次出现,我将非常感激。非常感谢您!

标签: firebaseflutterasynchronousdartnull

解决方案


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 返回的数据来构建小部件。相反,您的代码中发生的是:

  1. FutureBuilder 被创建。这调用_infoInit(),触发从 Firebase 获取数据;
  2. 调用 FutureBuilder 的 builder 方法。它尝试使用userName,但它为空,因此 Flutter 显示失败的断言;
  3. _infoInit()获取所有数据,并返回一个 Future(由于async方法签名中的子句,这个未来会自动返回;但是,如果没有return子句,它实际上不会返回任何数据)。但是尽管 Future 没有任何数据,状态中的 3 个变量(包括userName)已经更新,现在包含一些数据。
  4. 由于传递给 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,则使用起来可能更简单),但最终,这取决于您的偏好。


推荐阅读