【问题标题】:How to dynamically build rich text with FutureBuilder in Flutter如何在 Flutter 中使用 FutureBuilder 动态构建富文本
【发布时间】:2021-03-24 10:31:42
【问题描述】:

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

我得到的错误是这样的。

════════ 小部件库捕获的异常════════

在构建 FutureBuilder(dirty, state: _FutureBuilderState#151f7) 时抛出了以下断言: 构建函数返回 null。

有问题的小部件是:FutureBuilder 构建函数绝不能返回 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,
    );
  }

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

【问题讨论】:

  • return snapshot.data 之前尝试print(snapshot.data) - 你在日志上看到了什么?
  • @pskink 它打印 null 两次
  • 那么print(snapshot.error) 呢?
  • @pskink type '(dynamic) =&gt; Future&lt;TextSpan&gt;' is not a subtype of type '(String) =&gt; InlineSpan' of 'f'
  • 所以snapshot.error 不为空 - 这就是为什么你的builder 返回空 - 你必须先修复该错误(位于buildSelectableText

标签: flutter dart future flutter-futurebuilder


【解决方案1】:

很明显,您的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 是状态类中的 Future&lt;Map&lt;string, string&gt;&gt; 字段,您可以在 data['comment'] 更改时设置该字段:

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

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

【讨论】:

  • 我可以看到我的快照数据为空,因此我问这个问题。最初我试图只返回 TextSpan,但 FutureBuilder 不允许它,因为它不是小部件。你愿意告诉我如何改进这段代码吗?
  • 这就是我称之为“显而易见”的原因。您是否尝试过我建议的代码更改?
  • 我明白你在说什么。虽然我仍然不确定如何进行。 FutureBuilder 的最佳位置是在第二个 TextSpan 附近,但它不允许这样做,所以我不得不放置 SelectableText
  • 首先你要找出你的错误是什么。然后,您可能可以创建一个未来,以一种您可以同步使用的形式返回所有需要的数据。例如,您可以在未来构建一个(以@开头的单词)/显示名称的地图,然后在您的构建器中使用该地图,所有这些都完全同步。
  • @khagen 我添加了更多代码如何更改您的逻辑,而不是混合 UI 和业务逻辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-16
  • 2018-05-29
  • 1970-01-01
  • 1970-01-01
  • 2021-09-08
  • 2021-06-27
  • 1970-01-01
相关资源
最近更新 更多