我有一个非常简单(有状态)的小部件,其中包含一个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()
的执行触发的。
答案 0 :(得分:2)
您的代码可在DartPad.dev中使用
只需复制/粘贴此内容即可查看其运行情况:
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;
});
}
}
答案 1 :(得分:1)
我不知道您为什么说您的代码不起作用,但是在这里您可以看到甚至打印效果也应达到预期。您的示例可能过于简单。如果您在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, 2, 3, 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), () => [1, 2, 3, 4]);
}
}
Here是更详细的示例,其中对FutureBuilder的工作方式进行了完整的说明。花一些时间并仔细阅读它。这是Flutter提供的非常强大的功能。
答案 2 :(得分:0)
我有感觉,答案没有解决我的问题。我的问题是为什么小部件只能生成一次,为什么 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]
);
...
}