如何在Flutter中使用FutureBuilder动态构建富文本

How to dynamically build rich text with FutureBuilder in Flutter

我正在尝试在一段文本中查找“@”,然后异步收集该标签的显示名称。我 运行 遇到的问题是我的 FutureBuilder 一直返回 null。

我得到的错误是这样的。

════════ Exception caught by widgets library ════════

The following assertion was thrown building FutureBuilder(dirty, state: _FutureBuilderState#151f7): A build function returned null.

The offending widget is: FutureBuilder Build functions must never return null.

我的代码是这样的:

Container(
 width: double.infinity,
 child: FutureBuilder(
   initialData: SelectableText(''),
   future: buildSelectableText(context),
   builder: (BuildContext context, AsyncSnapshot<SelectableText> snapshot) {
     if (snapshot.connectionState == ConnectionState.waiting)
       return Center(child: CircularProgressIndicator());
     return snapshot.data;
   }
                ),
              ),

  Future<SelectableText> buildSelectableText(BuildContext context) async {
    return SelectableText.rich(
      TextSpan(
        text:'',
        children: data["comment"].split(' ').map<InlineSpan>((word) async {
          return word.startsWith('@') && word.length > 1 ?
          TextSpan(
            text: ' '+ (await context.read<UserDataModel>().fetchUser(word)).displayName,
            style: TextStyle(color: Colors.blue),
          ): TextSpan(text: ' ' + word);
        }).toList()
      ),
      textAlign: TextAlign.justify,
    );
  }

可能应该有一个简单的修复方法,但我找不到

显然,您返回时 snapshot.data 为空。

我会跳过连接状态,只查询 snapshot.hasDatasnapshot.hasError 以查明数据是否可用。

 builder: (BuildContext context, AsyncSnapshot<SelectableText> snapshot) {
   if (snapshot.hasData) {
     return snapshot.data;
   }

   // what if snapshot.hasError? 
   // you don't want to stay in loading mode forever in case of errors

   return Center(child: CircularProgressIndicator());
 }

也就是说,你应该在这里改进两件事:你的未来应该是你所在州的一个领域。如果您只是在每个构建周期重新创建它,那么在您不需要重新创建它的情况下会重新创建它。例如,如果用户将横向模式更改为纵向模式,它将一次又一次地 运行 ,尽管这根本不会改变他们的显示名称。但是你每次都会查询它,因为那是你设置构建函数的方式。

第二件事是您混合了数据检索和 UI 代码。您的未来正在获取数据。但是您将它与创建控件混合在一起。将您的 UI 代码放入构建函数中,并将您的异步数据检索代码移出那里。 数据检索代码是您构建器中应该拥有的未来。


这是实际执行获取数据的异步任务的函数:

  Future<Map<String, String>> CreateUserNameMap(String data, UserDataModel model) async {
    var userNames = data.split(' ')
                        .where((word)=> word.startsWith('@') && word.length > 1)
                        .toSet();
    
    var result = Map<String, String>();
    
    for(var name in userNames) {
      var user = await model.fetchUser(name);
      
      result[name] = user.displayName;
    }
    
    return result;
  }

现在您的 futureBuilder 可能如下所示:

FutureBuilder(
   future: gettingUserData,
   builder: (context, snapshot) {
     if (snapshot.hasData) {
       return SelectableText.rich(
          TextSpan(
            text:'',
            children: data["comment"].split(' ').map<InlineSpan>((word) {
              return word.startsWith('@') && word.length > 1 ?
              TextSpan(
                text: ' ' + snapshot.data[word]
               style: TextStyle(color: Colors.blue),
              ) : TextSpan(text: ' ' + word);
            }).toList()
          ),
          textAlign: TextAlign.justify,
        );
     }

     if(snapshot.hasError) {
         // you decide...
     }

     return Center(child: CircularProgressIndicator());
}

其中 gettingUserData 是您所在州 class 中的一个 Future<Map<string, string>> 字段,您在 data['comment'] 更改时设置:

gettingUserData = CreateUserNameMap(data['comment'], context.read<UserDataModel>());

一个好的地方可能是 initState。但也许您有不同的设置。