Flutter:setState不会触发构建

问题描述

我有一个非常简单(有状态)的小部件,其中包含一个Text小部件,该小部件显示列表的长度,该列表是小部件状态的成员变量。

initState()方法中,我使用setState()用包含四个元素的列表覆盖了list变量(以前为null)。但是,Text小部件仍显示为“ 0”。

添加图片暗示小部件的重建尚未触发,尽管我认为这是setState()方法的唯一目的。

以下代码

import 'package:Flutter/material.dart';

class Scan extends StatefulWidget {
  @override
  _ScanState createState() => _ScanState();
}

class _ScanState extends State<Scan> {
  List<int> numbers;

  @override
  void initState() {
    super.initState();
    _initializeController();
  }

  @override
  Widget build(BuildContext context) {
    print('Build was scheduled');
    return Center(
      child: Text(
        numbers == null ? '0' : numbers.length.toString()
      )
    );
  }

  Future<List<int>> _getAsyncNumberList() {
    return Future.delayed(Duration(seconds: 5),() => [1,2,3,4]);
  }

  _initializeController() async {
    List<int> newNumbersList = await _getAsyncNumberList();

    print("Number list was updated to list of length ${newNumbersList.length}");

    setState(() {
      numbers = newNumbersList;
    });
  }
}

我的问题:为什么小部件只能生成一次?我本来希望至少有两个版本,第二个版本是由setState()的执行触发的。

解决方法

您的代码可在DartPad.dev中使用

DartPad run

只需复制/粘贴此内容即可查看其运行情况:

import 'package:flutter/material.dart';

final Color darkBlue = Color.fromARGB(255,18,32,47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,home: Scaffold(
        body: Center(
          child: Scan(),),);
  }
}

class Scan extends StatefulWidget {
  @override
  _ScanState createState() => _ScanState();
}

class _ScanState extends State<Scan> {
  List<int> numbers;

  @override
  void initState() {
    super.initState();
    _initializeController();
  }

  @override
  Widget build(BuildContext context) {
    print('Build was scheduled');
    return Center(
      child: Text(
        numbers == null ? '0' : numbers.length.toString()
      )
    );
  }

  Future<List<int>> _getAsyncNumberList() {
    return Future(() => [1,2,3,4]);
  }

  _initializeController() async {
    List<int> newNumbersList = await _getAsyncNumberList();

    print("Number list was updated to list of length ${newNumbersList.length}");

    setState(() {
      numbers = newNumbersList;
    });
  }
}
,

Proof it works

我不知道您为什么说您的代码不起作用,但是在这里您可以看到甚至打印效果也应达到预期。您的示例可能过于简单。如果您在Future上添加延迟(这是真实情况,导致获取数据并等待它的确需要花费几秒钟的时间),那么代码确实显示为0。

您的代码现在可以工作的原因是,在构建方法开始呈现Widget之前,Future会立即返回列表。这就是为什么屏幕上显示的第一件事是4。

如果将.delayed()添加到Future中,则它确实会停止工作,因为一段时间后会检索数字列表,而构建会在更新数字之前呈现。

问题说明

代码中的

SetState没有正确调用。您要么这样做(在这种情况下就没有意义,因为您使用了“ await”,但通常也可以)

    _initializeController() async {
    setState(() {
    List<int> newNumbersList = await _getAsyncNumberList();

    print("Number list was updated to list of length ${newNumbersList.length}");

    
      numbers = newNumbersList;
    });
  }

或这样

    _initializeController() async {
    List<int> newNumbersList = await _getAsyncNumberList();

    print("Number list was updated to list of length ${newNumbersList.length}");

    
    numbers = newNumbersList;
    setState(() {
    /// this thing right here is an entire function. You MUST HAVE THE AWAIT in 
    /// the same function as the update,otherwise,the await is callledn,and on 
    /// another thread,the other functions are executed. In your case,this one 
    /// too. This one finishes early and updates nothing,and the await finishes later.
    });
  }

建议的解决方案

在等待5秒钟以使Future返回带有数据的新列表时,它将显示0,然后将显示4。如果要在等待数据的同时显示其他内容,请使用FutureBuilder窗口小部件。

没有FutureBuilder的完整代码:

class Scan extends StatefulWidget {
  @override
  _ScanState createState() => _ScanState();
}

class _ScanState extends State<Scan> {
  List<int> numbers;

  @override
  void initState() {
    super.initState();
    _initializeController();
  }

  @override
  Widget build(BuildContext context) {
    print('Build was scheduled');

    return Center(
        child: Text(numbers == null ? '0' : numbers.length.toString()));
  }

  Future<List<int>> _getAsyncNumberList() {
    return Future.delayed(Duration(seconds: 5),() => [1,4]);
  }

  _initializeController() async {
      List<int> newNumbersList = await _getAsyncNumberList();

      print(
          "Number list was updated to list of length ${newNumbersList.length}");
      numbers = newNumbersList;
      setState(() {});
  }
}

我强烈建议使用此版本,因为它在等待数据时始终向用户显示某些内容,并且在出现错误时也具有故障保护功能。尝试一下,然后选择最适合您的东西,但是我再次推荐这个。

使用FutureBuilder的完整代码:

class _ScanState extends State<Scan> {
  List<int> numbers;

  @override
  void initState() {
    super.initState();

  }

  @override
  Widget build(BuildContext context) {
    print('Build was scheduled');

    return FutureBuilder(
      future: _getAsyncNumberList(),builder: (BuildContext context,AsyncSnapshot<List<int>> snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.waiting: return Center(child: Text('Fetching numbers...'));
          default:
            if (snapshot.hasError)
              return Center(child: Text('Error: ${snapshot.error}'));
            else
              /// snapshot.data is the result that the async function returns
              return Center(child: Text('Result: ${snapshot.data.length}'));
        }
      },);
  }

  Future<List<int>> _getAsyncNumberList() {
    return Future.delayed(Duration(seconds: 5),4]);
  }
}

Here是更详细的示例,其中对FutureBuilder的工作方式进行了完整的说明。花一些时间并仔细阅读它。这是Flutter提供的非常强大的功能。

,

我有感觉,答案没有解决我的问题。我的问题是为什么小部件只能生成一次,为什么 setState()不会触发第二次生成

诸如“使用FutureBuilder”之类的答案没有帮助,因为它们完全绕过有关setState()的问题。因此,无论异步功能完成多久,执行setState()时,触发重建都应使用新列表更新UI。

此外,异步功能不会完成得太早(在构建完成之前)。我通过尝试更改的WidgetsBinding.instance.addPostFrameCallback来确保它没有:没有。

我发现问题出在其他地方。在我的main()函数中,前两行是:

  SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
  SystemChrome.setPreferredOrientations(
    [DeviceOrientation.portraitUp,DeviceOrientation.portraitDown]
  );

这以某种方式影响了构建顺序。但是仅在我的Huawei P20 Lite上,在我的其他测试设备上,没有在模拟器中,也没有在Dartpad上。

所以结论: 代码很好。我对setState()的理解也很好。我没有为您提供足够的上下文来重现该错误。我的解决方案是使main()函数的前两行异步:

void main() async {
  await SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
  await SystemChrome.setPreferredOrientations(
    [DeviceOrientation.portraitUp,DeviceOrientation.portraitDown]
  );
  ...
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...