Flutter:setState() 不会触发构建
Flutter: setState() does not trigger a build
我有一个非常简单的(有状态的)小部件,其中包含一个 Text
小部件,它显示列表的长度,该列表是小部件状态的成员变量。
在 initState()
方法中,我使用 setState()
将列表变量(以前为 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
中有效
就copy/paste这个看看吧运行:
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;
});
}
}
我不知道你为什么说你的代码不工作,但在这里你可以看到即使是印刷品也能正常工作。您的示例可能过于简单。如果你给那个 Future 添加一个延迟(这是一个真实的案例场景,导致获取数据并等待它有时需要几秒钟),那么代码确实显示 0.
您的代码现在可以正常工作的原因是 Future return 在构建方法开始呈现 Widgets 之前立即列出列表。这就是为什么首先出现在屏幕上的是 4。
如果将 .delayed() 添加到 Future,那么它确实会停止工作,因为数字列表会在一段时间后检索,并且构建会在数字更新之前呈现。
问题解释
您的代码中的 SetState 未正确调用。你要么这样做(在这种情况下没有意义,因为你使用“等待”,但通常它也有效)
_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 到 return 包含数据的新列表时显示 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]);
}
}
是一个更详细的示例,完整解释了 FutureBuilder 的工作原理。花一些时间仔细阅读它。这是 Flutter 提供的一个非常强大的东西。
我有一种感觉,答案没有解决我的问题。我的问题是 为什么小部件只构建一次 以及为什么 setState() 不触发 第二个 build .
像“使用 FutureBuilder”这样的答案没有帮助,因为它们完全绕过了 setState()
的问题。因此,无论异步函数完成多晚,触发重建都应该在执行 setState()
时用新列表更新 UI。
此外,异步函数不会过早完成(在构建完成之前)。我通过尝试 WidgetsBinding.instance.addPostFrameCallback
来确保它不会改变:没有。
我发现问题出在其他地方。在我的 main()
函数中,前两行是:
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp,DeviceOrientation.portraitDown]
);
这在某种程度上影响了构建顺序。但只在我的华为 P20 Lite 上,没有在我的其他测试设备上,不在模拟器中,也不在 Dartpad 上。
所以结论:
代码很好。我对setState()
的理解也还行。我没有为您提供足够的上下文来重现错误。我的解决方案是使 main()
函数中的前两行异步:
void main() async {
await SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
await SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp,DeviceOrientation.portraitDown]
);
...
}
我有一个非常简单的(有状态的)小部件,其中包含一个 Text
小部件,它显示列表的长度,该列表是小部件状态的成员变量。
在 initState()
方法中,我使用 setState()
将列表变量(以前为 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
中有效就copy/paste这个看看吧运行:
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;
});
}
}
我不知道你为什么说你的代码不工作,但在这里你可以看到即使是印刷品也能正常工作。您的示例可能过于简单。如果你给那个 Future 添加一个延迟(这是一个真实的案例场景,导致获取数据并等待它有时需要几秒钟),那么代码确实显示 0.
您的代码现在可以正常工作的原因是 Future return 在构建方法开始呈现 Widgets 之前立即列出列表。这就是为什么首先出现在屏幕上的是 4。
如果将 .delayed() 添加到 Future,那么它确实会停止工作,因为数字列表会在一段时间后检索,并且构建会在数字更新之前呈现。
问题解释
您的代码中的 SetState 未正确调用。你要么这样做(在这种情况下没有意义,因为你使用“等待”,但通常它也有效)
_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 到 return 包含数据的新列表时显示 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]);
}
}
我有一种感觉,答案没有解决我的问题。我的问题是 为什么小部件只构建一次 以及为什么 setState() 不触发 第二个 build .
像“使用 FutureBuilder”这样的答案没有帮助,因为它们完全绕过了 setState()
的问题。因此,无论异步函数完成多晚,触发重建都应该在执行 setState()
时用新列表更新 UI。
此外,异步函数不会过早完成(在构建完成之前)。我通过尝试 WidgetsBinding.instance.addPostFrameCallback
来确保它不会改变:没有。
我发现问题出在其他地方。在我的 main()
函数中,前两行是:
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp,DeviceOrientation.portraitDown]
);
这在某种程度上影响了构建顺序。但只在我的华为 P20 Lite 上,没有在我的其他测试设备上,不在模拟器中,也不在 Dartpad 上。
所以结论:
代码很好。我对setState()
的理解也还行。我没有为您提供足够的上下文来重现错误。我的解决方案是使 main()
函数中的前两行异步:
void main() async {
await SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
await SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp,DeviceOrientation.portraitDown]
);
...
}