build 方法在 initState 完成之前运行

Jin*_*Kim 8 flutter

我正在尝试用 Flutter 制作一个天气应用程序。但由于某种原因,build() 方法在 initState() 方法完成之前运行。问题是,所有状态变量都是使用 initState() 内的 setState() 方法初始化的,其变量将在 build() 方法中使用。我猜问题是 Flutter 试图在 setState() 方法之前访问这些状态变量,这会不断向我抛出错误:必须向 Text 小部件提供非空字符串。我知道这些代码太长,难以阅读。但如果你能帮我解决这个问题,我将不胜感激。

\n
import 'package:flutter/material.dart';\n\nimport "package:climat/screens/LoadingScreen.dart";\nimport "package:climat/screens/MainScreen.dart";\nimport "package:climat/screens/SearchScreen.dart";\n\nvoid main() {\n  runApp(\n    MaterialApp(\n      theme: ThemeData(\n        fontFamily: "Open Sans",\n      ),\n      title: "Climat",\n      initialRoute: "/",\n      onGenerateRoute: (RouteSettings routeSettings) {\n        dynamic routes = <String, WidgetBuilder>{\n          "/": (context) => LoadingScreen(),\n          "/index": (context) => MainScreen(),\n          "/search": (context) => SearchScreen(),\n        };\n        WidgetBuilder builder = routes[routeSettings.name];\n        return MaterialPageRoute(builder: (context) => builder(context));\n      },\n    ),\n  );\n}\n
Run Code Online (Sandbox Code Playgroud)\n
import "package:flutter/material.dart";\nimport "package:flutter_spinkit/flutter_spinkit.dart";\n\nimport "package:climat/services/GeolocatorHelper.dart";\nimport "package:climat/services/NetworkHelper.dart";\n\nimport "package:climat/utilities/constants.dart";\n\nclass LoadingScreen extends StatefulWidget {\n  @override\n  _LoadingScreenState createState() => _LoadingScreenState();\n}\n\nclass _LoadingScreenState extends State<LoadingScreen> {\n  Future<void> getWeatherData() async {\n    Map<String, double> coordinates = await GeolocatorHelper().getGeoData();\n    final NetworkHelper networkHelper = NetworkHelper(\n        uri:\n            "$endPoint&lat=${coordinates["latitude"]}&lon=${coordinates["longitude"]}");\n    final dynamic data = await networkHelper.getData();\n    return await Navigator.pushNamed(context, "/index", arguments: data);\n  }\n\n  @override\n  void initState() {\n    this.getWeatherData();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: Text("Climat"),\n      ),\n      body: SafeArea(\n        child: Center(\n          child: SpinKitRing(\n            color: Colors.redAccent,\n          ),\n        ),\n      ),\n    );\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
import "package:flutter/material.dart";\n\nimport "package:climat/services/WeatherHelper.dart";\nimport "package:climat/services/NetworkHelper.dart";\n\nimport "package:climat/utilities/constants.dart";\n\nclass MainScreen extends StatefulWidget {\n  @override\n  _MainScreenState createState() => _MainScreenState();\n}\n\nclass _MainScreenState extends State<MainScreen> {\n  Color backgroundColor;\n  String cityName;\n  int temperature;\n  String status;\n  String image;\n  int minTemperature;\n  int maxTemperature;\n  int humidity;\n\n  Future<void> updateUI({String userInput = null}) async {\n    dynamic weatherData;\n    if (userInput == null) {\n      weatherData = ModalRoute.of(context).settings.arguments;\n    } else {\n      final NetworkHelper networkHelper =\n          NetworkHelper(uri: "$endPoint&q=$userInput");\n      weatherData = await networkHelper.getData();\n    }\n    final int weatherCode = weatherData["weather"][0]["id"];\n    final WeatherHelper weatherHelper = WeatherHelper(\n      weatherCode: weatherCode,\n    );\n    setState(() {\n      this.backgroundColor = weatherHelper.getBackgroundColor();\n      this.cityName = weatherData["name"];\n      this.temperature = weatherData["main"]["temp"].toInt();\n      this.status = weatherHelper.getWeatherStatus();\n      this.minTemperature = weatherData["main"]["temp_min"].toInt();\n      this.maxTemperature = weatherData["main"]["temp_max"].toInt();\n      this.humidity = weatherData["main"]["humidity"];\n      this.image = weatherHelper.getWeatherImage();\n    });\n  }\n\n  @override\n  void initState() {\n    this.updateUI();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      backgroundColor: this.backgroundColor,\n      appBar: AppBar(\n        title: Text("Climat"),\n      ),\n      body: SafeArea(\n        child: Center(\n          child: Column(\n            mainAxisSize: MainAxisSize.min,\n            children: [\n              Text(\n                this.cityName,\n                style: TextStyle(\n                  fontSize: 24.0,\n                  color: Colors.white,\n                ),\n              ),\n              SizedBox(\n                height: 30.0,\n              ),\n              Row(\n                mainAxisAlignment: MainAxisAlignment.center,\n                children: [\n                  Image.asset(\n                    "./assets/images/${this.image}",\n                    scale: 4.0,\n                  ),\n                  SizedBox(\n                    width: 30.0,\n                  ),\n                  Text(\n                    "${this.temperature}\xc2\xb0C",\n                    style: TextStyle(\n                      fontSize: 96.0,\n                      color: Colors.white,\n                    ),\n                  ),\n                ],\n              ),\n              SizedBox(\n                height: 30.0,\n              ),\n              Text(\n                this.status.toUpperCase(),\n                style: TextStyle(\n                  fontSize: 24.0,\n                  color: Colors.white,\n                ),\n              ),\n              SizedBox(\n                height: 10.0,\n              ),\n              Text(\n                "MIN / MAX : ${this.minTemperature.toString()} / ${this.maxTemperature.toString()}",\n                style: TextStyle(\n                  fontSize: 24.0,\n                  color: Colors.white,\n                ),\n              ),\n              SizedBox(\n                height: 10.0,\n              ),\n              Text(\n                "HUMIDITY : ${this.humidity}%",\n                style: TextStyle(\n                  fontSize: 24.0,\n                  color: Colors.white,\n                ),\n              ),\n              SizedBox(\n                height: 30.0,\n              ),\n              ElevatedButton(\n                onPressed: () async {\n                  dynamic userInput =\n                      await Navigator.pushNamed(context, "/search");\n                  this.updateUI(\n                    userInput: userInput.toString(),\n                  );\n                },\n                style: ElevatedButton.styleFrom(\n                  padding: EdgeInsets.symmetric(\n                    vertical: 8.0,\n                    horizontal: 16.0,\n                  ),\n                ),\n                child: Text(\n                  "Search by city name",\n                  style: TextStyle(\n                    fontSize: 20.0,\n                  ),\n                ),\n              ),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Sam*_*han 13

如果您想Future在中使用函数initState并希望它运行一次并在此之前完成build,请考虑使用WidgetsBinding.instance.addPostFrameCallback,例如

@override
void initState() {
    WidgetsBinding.instance.addPostFrameCallback((_) async {
        await this.getWeatherData();
        setState(() { });
    });
}
Run Code Online (Sandbox Code Playgroud)

@override
void initState() {
  WidgetsBinding.instance.addPostFrameCallback((_) async {
    await this.updateUI();
    setState(() { });        
  });
}
Run Code Online (Sandbox Code Playgroud)

setState应该用于重建小部件

  • 这个答案可能会解决问题(或多或少),但它不能确保“initState”中的代码在“build”运行之前完成。`addPostFrameCallback`,顾名思义是“后帧”,在 `build` 方法完成后运行代码。相反,请在构建方法中使用“FutureBuilder”。 (2认同)