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

如何在Dart中实现无需外部异步等待的MyDatabase单例?

解决Flutter中Floor数据库单例全局访问的问题

你的核心问题是异步初始化的单例无法在Widget层级中便捷访问,需要手动传递实例。这里有几个实用的解决方案,你可以根据项目复杂度选择:


方案1:全局单例+FutureBuilder(快速实现)

首先优化你的单例类,添加一个静态的instance getter,确保只初始化一次:

class MydbModel {
  UserDao _userDao;
  MydbModel._(this._userDao);
  static MydbModel? _instance; // 全局唯一实例

  // 全局访问入口,自动处理初始化
  static Future<MydbModel> get instance async {
    if (_instance == null) {
      _instance = MydbModel._(await initialDatabase());
    }
    return _instance!;
  }

  static Future<UserDao> initialDatabase() async {
    var db = await $FloorAppDatabase.databaseBuilder('flutter_database.db').build();
    return db.userDao;
  }

  UserDao get userDao => _userDao;
}

然后在App的根Widget中使用FutureBuilder等待数据库初始化完成,再渲染页面:

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FutureBuilder<MydbModel>(
        future: MydbModel.instance,
        builder: (context, snapshot) {
          // 初始化中显示加载动画
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Scaffold(body: Center(child: CircularProgressIndicator()));
          }
          // 初始化失败显示错误
          else if (snapshot.hasError) {
            return Scaffold(body: Center(child: Text('数据库初始化失败: ${snapshot.error}')));
          }
          // 初始化完成,传递实例给子页面
          else {
            return HomePage(dbModel: snapshot.data!);
          }
        },
      ),
    );
  }
}

在StatefulWidget中就可以直接使用了:

class HomePage extends StatefulWidget {
  final MydbModel dbModel;

  const HomePage({super.key, required this.dbModel});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('数据库示例')),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // 直接调用userDao的方法
            final users = await widget.dbModel.userDao.getAllUsers();
            print('获取到用户:$users');
          },
          child: const Text('获取所有用户'),
        ),
      ),
    );
  }
}

方案2:提前初始化数据库(无加载状态)

如果可以接受启动时短暂的等待,你可以在runApp前完成数据库初始化,这样整个App启动后就能直接使用实例:

void main() async {
  // 必须调用这个,确保Flutter绑定初始化完成
  WidgetsFlutterBinding.ensureInitialized();
  // 提前初始化数据库单例
  final dbModel = await MydbModel.instance;
  runApp(MyApp(dbModel: dbModel));
}

class MyApp extends StatelessWidget {
  final MydbModel dbModel;

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DbProvider(
        // 用InheritedWidget全局共享实例,避免手动传递
        dbModel: dbModel,
        child: const HomePage(),
      ),
    );
  }
}

// 自定义InheritedWidget,方便全局获取实例
class DbProvider extends InheritedWidget {
  final MydbModel dbModel;

  const DbProvider({super.key, required this.dbModel, required super.child});

  static DbProvider of(BuildContext context) {
    final provider = context.dependOnInheritedWidgetOfExactType<DbProvider>();
    assert(provider != null, '找不到DbProvider,请确保在根Widget中包裹');
    return provider!;
  }

  @override
  bool updateShouldNotify(DbProvider oldWidget) => dbModel != oldWidget.dbModel;
}

之后在任意State中都可以这样获取实例:

class _HomePageState extends State<HomePage> {
  late UserDao _userDao;

  @override
  void initState() {
    super.initState();
    // 全局获取数据库实例
    _userDao = DbProvider.of(context).dbModel.userDao;
  }

  // 后续直接使用_userDao即可
}

方案3:使用状态管理库(适合复杂应用)

如果你的项目较大,推荐使用Provider/Riverpod这类状态管理库,将数据库实例注入全局状态,无需手动传递:

以Riverpod为例(需要添加flutter_riverpod依赖):

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

// 定义数据库实例的Provider
final dbModelProvider = FutureProvider<MydbModel>((ref) async {
  return await MydbModel.instance;
});

class MyApp extends ConsumerWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final dbAsync = ref.watch(dbModelProvider);
    return MaterialApp(
      home: dbAsync.when(
        loading: () => const Scaffold(body: Center(child: CircularProgressIndicator())),
        error: (err, stack) => Scaffold(body: Center(child: Text('初始化错误:$err'))),
        data: (dbModel) => HomePage(dbModel: dbModel),
      ),
    );
  }
}

在子Widget中只需通过ref.watch就能获取实例,非常便捷。


纠正你之前的错误写法

你尝试的.then写法返回的是Future<MydbModel>对象,不是实际的实例,所以print(userDao)会输出Future的状态。正确的写法应该是在回调里处理实例:

main() {
  MydbModel.create().then((dbModel){
    print(dbModel.userDao); // 这里才能拿到真正的实例
  });
}

但这种写法在Widget中无法直接使用,所以更推荐上面的几种方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:13:10