Flutter问题:确保类成员赋值完成后再渲染应用界面
Flutter异步加载本地JSON数据后再渲染界面的问题
我是Flutter新手,尝试从本地JSON文件读取数据并渲染到界面。当前逻辑中,类构造函数里的异步JSON读取操作还没完成变量赋值,界面就提前渲染了,导致先显示变量的初始值(比如"not populated")。虽然SampleDataClass中的print语句能正常输出JSON里的正确数据,但界面总是先展示初始值,我需要等构造函数完成成员变量赋值后,再把实例交给build组件渲染界面。
复现代码
main.dart
import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'sampleDataClass.dart'; import 'package:provider/provider.dart'; String assetFilePath = 'assets/basicData.json'; void main() async { WidgetsFlutterBinding.ensureInitialized(); FlutterError.onError = (details) { FlutterError.presentError(details); if (kReleaseMode) exit(1); }; runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => MyAppState(), child: const MaterialApp( title: "Sample screen", home: MyHomePage(), ) ); } } class MyAppState extends ChangeNotifier { SampleDataClass current = SampleDataClass(assetFilePath); } class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { var myAppState = context.watch<MyAppState>(); var myAppBasicData = myAppState.current; return Scaffold( appBar: AppBar( backgroundColor: Colors.indigo, foregroundColor: Colors.amberAccent, title: const Text("This is the App Bar"), elevation: 10, ), body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Expanded( child: Container( color: Colors.blueGrey, padding: const EdgeInsets.all(10), child: Text(myAppBasicData.language, style: const TextStyle( color: Colors.white, )))), ]), ] ), ); } }
SampleDataClass.dart
import 'dart:core'; import 'dart:convert'; import 'package:flutter/services.dart' show rootBundle; class SampleDataClass { String classFilePath = ""; String language = "not populated"; String batteryName = "not populated"; SampleDataClass(filePath) { rootBundle.loadString(filePath).then((jsonDataString) { Map classDataMap = jsonDecode(jsonDataString); language = classDataMap['language']; print(language); batteryName = classDataMap['batteryName']; print(batteryName); }); } Map<String, dynamic> toJson() => { 'language': language, 'batteryName': batteryName, }; }
pubspec.yaml
name: samples description: A new Flutter project. environment: sdk: '>=2.18.6 <3.0.0' dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 provider: ^4.1.1 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 flutter: uses-material-design: true assets: - assets/basicData.json
basicData.json
{ "language": "English", "batteryName": "Exercise 32" }
解决方案
问题核心在于普通构造函数无法处理异步操作,rootBundle.loadString是异步方法,构造函数会直接返回实例,不会等待异步操作完成。同时,数据更新后没有通知UI刷新,所以即使后续变量赋值了,界面也不会更新。可以通过以下步骤修复:
1. 修改SampleDataClass为异步工厂构造
把普通构造改成异步工厂方法,确保数据加载完成后再返回实例:
import 'dart:core'; import 'dart:convert'; import 'package:flutter/services.dart' show rootBundle; class SampleDataClass { String classFilePath = ""; String language = "not populated"; String batteryName = "not populated"; // 私有构造,防止直接实例化 SampleDataClass._private(); // 异步工厂方法,加载完成后返回实例 static Future<SampleDataClass> fromAsset(String filePath) async { final instance = SampleDataClass._private(); final jsonDataString = await rootBundle.loadString(filePath); final classDataMap = jsonDecode(jsonDataString) as Map<String, dynamic>; instance.language = classDataMap['language']; instance.batteryName = classDataMap['batteryName']; instance.classFilePath = filePath; return instance; } Map<String, dynamic> toJson() => { 'language': language, 'batteryName': batteryName, }; }
2. 修改MyAppState处理异步初始化并通知UI
在状态类中用Future管理SampleDataClass实例,加载完成后调用notifyListeners()通知UI刷新:
class MyAppState extends ChangeNotifier { Future<SampleDataClass>? _dataFuture; MyAppState() { // 初始化时启动异步加载 _dataFuture = SampleDataClass.fromAsset(assetFilePath); // 加载完成后通知UI _dataFuture?.then((_) => notifyListeners()); } Future<SampleDataClass>? get dataFuture => _dataFuture; }
3. 在UI层处理加载状态
使用FutureBuilder等待数据加载完成,期间显示加载指示器,加载完成后再渲染数据:
class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { var myAppState = context.watch<MyAppState>(); return Scaffold( appBar: AppBar( backgroundColor: Colors.indigo, foregroundColor: Colors.amberAccent, title: const Text("This is the App Bar"), elevation: 10, ), body: FutureBuilder<SampleDataClass>( future: myAppState.dataFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { // 加载中显示指示器 return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { // 加载失败显示错误信息 return Center(child: Text('加载失败: ${snapshot.error}')); } else if (snapshot.hasData) { // 加载完成显示数据 final data = snapshot.data!; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Expanded( child: Container( color: Colors.blueGrey, padding: const EdgeInsets.all(10), child: Text(data.language, style: const TextStyle( color: Colors.white, )))), ]), ] ); } else { return const Center(child: Text('无数据')); } }, ), ); } }
4. 其他注意事项
- 确保
pubspec.yaml中正确配置了assets路径,并且执行过flutter pub get - 如果你不想用
FutureBuilder,也可以在MyAppState中保存已加载的实例,加载完成后更新实例并调用notifyListeners(),UI层监听状态变化即可
内容的提问来源于stack exchange,提问作者Jaime
相关产品推荐
相关产品推荐

