ValueNotifier:在页面构建完成之前更新小部件的文本引发异常

Posted

技术标签:

【中文标题】ValueNotifier:在页面构建完成之前更新小部件的文本引发异常【英文标题】:ValueNotifier: update widget's text before page's build completes throws exceptions 【发布时间】:2021-11-05 12:23:49 【问题描述】:

我正在尝试计算百分比并将其作为文本设置为文本小部件,同时构建页面。在我的应用程序中,我有一个带有 ListTile 的设置页面,通过点击它可以将用户带到一个新页面。在新页面 (percentage_settings_page.dart) 中,我从我的 SQLITE 数据库中读取数据,其中包含一些使用 FutureBuilder<List<User>>ListView.builder() 的用户。

在构建包含每个用户的名称和百分比的列表视图时,我需要计算剩余百分比,这是通过从 100 中减去用户百分比的总和得出的,并将其设置在循环指示器中。我正在尝试使用ValueNotifier 来实现这一目标。页面是这样的:

How it is: displaying 100

How it should be

percentage_settings_page.dart:

class _PercentageSettingsPageState extends State<PercentageSettingsPage> 
  /* other variables */

  final ValueNotifier<double> _myPercentage = ValueNotifier<double>(100);

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: const Text('Divide percentage'),
        leading: BackButton(
          onPressed: () async => Navigator.pop(context),
        ),
      ),
      body: SafeArea(
        child: Column(
          children: [
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: Align(
                alignment: Alignment.topCenter,
                child: CircularPercentIndicator(
                  radius: 120.0,
                  lineWidth: 10.0,
                  animation: true,
                  center: ValueListenableBuilder(
                      valueListenable: _myPercentage,
                      builder:
                          (BuildContext context, double value, Widget? child) 
                        return Text(
                          '$value', // <- this text should be updated
                          style: const TextStyle(
                              fontWeight: FontWeight.bold, fontSize: 20.0),
                        );
                      ),
                  footer: const Text(
                    'Remaining percentage',
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontSize: 17.0),
                  ),
                  circularStrokeCap: CircularStrokeCap.round,
                  progressColor: Colors.orange,
                ),
              ),
            ),
            SafeArea(
              child: _buildUsersFromDB(),
            ),
          ],
        ),
      ),
    );
  

  Widget _buildUsersFromDB() 
    return FutureBuilder<List<User>>(
        future: _userDAO.readData(),
        builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) 
          return snapshot.hasData
              ? ListView.builder(
                  itemCount: snapshot.data!.length,
                  shrinkWrap: true,
                  itemBuilder: (BuildContext context, int i) 
                    return Card(
                      elevation: 2,
                      margin: const EdgeInsets.all(5),
                      child: _buildRow(snapshot.data![i]),
                    );
                  ,
                )
              : const Center(
                  child: CircularProgressIndicator(),
                );
        );
  

  Widget _buildRow(User user) 
    _myPercentage.value -= user.percentage; // <- This line gives me problems
    return ListTile(
      title: Text(
        user.name,
        style: _biggerFont,
      ),
      trailing: Text('$user.percentage %'),
      onTap: () => _showDoubleDialog(user).then((value) 
        /* some code */
      ),
    );
  

但是当我运行应用程序时出现以下错误:

════════ Exception caught by foundation library ════════════════════════════════
The following assertion was thrown while dispatching notifications for ValueNotifier<double>:
setState() or markNeedsBuild() called during build.

This ValueListenableBuilder<double> widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: ValueListenableBuilder<double>
    state: _ValueListenableBuilderState<double>#5709e
The widget which was currently being built when the offending call was made was: SliverList
    delegate: SliverChildBuilderDelegate#e6e08(estimated child count: 3)
    renderObject: RenderSliverList#aacf6 relayoutBoundary=up15 NEEDS-LAYOUT NEEDS-PAINT
When the exception was thrown, this was the stack
#0      Element.markNeedsBuild.<anonymous closure>
package:flutter/…/widgets/framework.dart:4217
#1      Element.markNeedsBuild
package:flutter/…/widgets/framework.dart:4232
#2      State.setState
package:flutter/…/widgets/framework.dart:1108
#3      _ValueListenableBuilderState._valueChanged
package:flutter/…/widgets/value_listenable_builder.dart:182
#4      ChangeNotifier.notifyListeners
package:flutter/…/foundation/change_notifier.dart:243
...
The ValueNotifier<double> sending notification was: ValueNotifier<double>#5ee2d(89.2)

有人知道我如何实现这一点,或者在构建期间/之后尝试设置小部件时的最佳做法是什么?

编辑:我需要 ValueNotifier,因为每当更新单个用户的百分比时我需要更新剩余百分比(通过点击用户并选择新百分比来完成)

【问题讨论】:

【参考方案1】:

我只需将调用移动到_userDAO.readData() 并添加标题文本和基于此的列表项。这将消除对 _myPercentage ValueNotifier 的需要。

【讨论】:

以上是关于ValueNotifier:在页面构建完成之前更新小部件的文本引发异常的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 异步加载多个函数一个接一个

“ValueNotifier”+“ValueListenableBuilder”是不是适用于 Flutter 中的“许多”小部件?

我应该自己处置 ValueNotifier 对象吗?保持原样安全吗?

有没有办法让 cURL 等到页面的动态更新完成?

Flutter 实现局部刷新 StreamBuilder 实例详解

Flutter 实现局部刷新 StreamBuilder 实例详解