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

Flutter启动初始化变量问题:SharedPreferences异步致空值崩溃

解决Flutter异步初始化变量导致build空值崩溃的问题

这个问题我太熟悉了——Flutter里异步操作和Widget构建的顺序冲突确实很容易踩坑!你遇到的核心问题就是初始化依赖异步操作,但build方法会在异步结果返回前就执行,导致空值引发崩溃。不管是SharedPreferences还是JSON文件读写,本质都是异步操作(哪怕你用了同步方法,获取目录的getApplicationDocumentsDirectory()还是异步的),所以得从「等待异步完成再构建UI」或者「提前完成异步初始化」这两个方向入手。下面给你几个靠谱的解决思路:

方案1:用FutureBuilder等待异步结果(最推荐的局部UI方案)

FutureBuilder专门用来处理「依赖异步数据构建UI」的场景,它会自动监听Future的状态,在异步完成前显示加载状态,完成后再用数据构建UI,完美避开空值问题。

修改你的SharedPreferences示例代码:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class TestingApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _CupertinoNavigationState();
}

class _CupertinoNavigationState extends State<TestingApp> {
  Future<int>? _itemNoFuture;

  @override
  void initState() {
    super.initState();
    // 提前准备好获取数据的Future
    _itemNoFuture = SharedPreferences.getInstance().then((sp) {
      return sp.getInt("itemNo") ?? 0; // 给个默认值避免null
    });
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<int>(
      future: _itemNoFuture,
      builder: (context, snapshot) {
        // 异步还在进行时,显示加载指示器
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const CircularProgressIndicator();
        }
        // 异步完成,获取数据(这里已经不会是null了)
        int itemNo = snapshot.data!;
        print("item number on build " + itemNo.toString());
        return Text("当前itemNo: $itemNo");
      },
    );
  }
}

方案2:在initState中用异步函数初始化(需处理加载状态)

initState本身不能标记为async,但你可以在里面启动一个异步函数,获取到值后调用setState更新。不过这个方法会先执行一次build(此时变量为空),所以必须在build里处理空值的情况,比如显示加载中。

示例代码:

class _CupertinoNavigationState extends State<TestingApp> {
  int? itemNo;

  @override
  void initState() {
    super.initState();
    // 立即执行异步初始化函数
    _loadItemNo();
  }

  Future<void> _loadItemNo() async {
    final sp = await SharedPreferences.getInstance();
    setState(() {
      itemNo = sp.getInt("itemNo") ?? 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    // 变量未初始化时显示加载
    if (itemNo == null) {
      return const CircularProgressIndicator();
    }
    print("item number on build " + itemNo.toString());
    return Text("当前itemNo: $itemNo");
  }
}

方案3:全局提前初始化(适合全局通用配置)

如果这个变量是整个APP都需要用到的,你可以在main函数里先完成异步初始化,再启动APP。这样APP启动后变量就已经准备好了,不会有空值问题。

示例代码:

void main() async {
  // 必须加这句,确保Flutter初始化完成
  WidgetsFlutterBinding.ensureInitialized();
  
  // 提前初始化SharedPreferences并获取值
  final sp = await SharedPreferences.getInstance();
  int globalItemNo = sp.getInt("itemNo") ?? 0;

  // 启动APP,把全局值传递给需要的页面
  runApp(MyApp(initialItemNo: globalItemNo));
}

class MyApp extends StatelessWidget {
  final int initialItemNo;

  const MyApp({super.key, required this.initialItemNo});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: TestingApp(itemNo: initialItemNo),
    );
  }
}

// 页面直接使用初始化好的值
class TestingApp extends StatefulWidget {
  final int itemNo;

  const TestingApp({super.key, required this.itemNo});

  @override
  State<StatefulWidget> createState() => _CupertinoNavigationState();
}

class _CupertinoNavigationState extends State<TestingApp> {
  late int itemNo;

  @override
  void initState() {
    super.initState();
    itemNo = widget.itemNo;
  }

  @override
  Widget build(BuildContext context) {
    print("item number on build " + itemNo.toString());
    return Text("当前itemNo: $itemNo");
  }
}

为什么你的JSON方案不行?

你在initState里调用fetchSavedItemNo(),但这个方法里用了getApplicationDocumentsDirectory().then(...),是异步操作,所以fetchSavedItemNo()返回的其实是Future<int>,而不是实际的int值,直接赋值给itemNo自然是null。你可以把fetchSavedItemNo()改成async函数,然后用方案2的方式调用它。


内容的提问来源于stack exchange,提问作者Simon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:20:26