如何在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
相关产品推荐
相关产品推荐

