You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 08:50:38