Flutter中无法在Main.dart及UI代码正确使用SharedPreferences的问题
核心问题分析
- 重复获取SharedPreferences实例:你的
getSharedPrefs()每次调用都会重新执行SharedPreferences.getInstance(),就算main里提前await了,每次调用业务方法还是会重新发起实例请求,导致提前初始化的操作完全无效。 - UI中无法直接处理异步方法:Flutter的UI渲染是同步流程,不能直接在build方法里使用await,必须通过异步组件或状态管理来处理Future类型的数据。
解决方案步骤
1. 改造存储服务,缓存SharedPreferences实例
修改KeyValueStorageServiceImpl,把SharedPreferences实例保存为类成员,确保只初始化一次:
import 'key_value_class.dart'; import 'package:shared_preferences/shared_preferences.dart'; class KeyValueStorageServiceImpl extends KeyValueStorageService { // 缓存SharedPreferences实例,避免重复初始化 SharedPreferences? _prefs; // 全局初始化方法,仅需调用一次 Future<void> init() async { _prefs ??= await SharedPreferences.getInstance(); } // 私有方法,确保实例已完成初始化 SharedPreferences get _instance { if (_prefs == null) { throw StateError('请先调用init()初始化KeyValueStorageService'); } return _prefs!; } @override Future<T?> getValue<T>(String key) async { final prefs = _instance; switch (T) { case String: return prefs.getString(key) as T?; case int: return prefs.getInt(key) as T?; case double: return prefs.getDouble(key) as T?; case bool: return prefs.getBool(key) as T?; case List: return prefs.getStringList(key) as T?; default: throw UnimplementedError('未实现${T.runtimeType}类型的读取逻辑'); } } @override Future<bool> removeKey(String key) async { return _instance.remove(key); } @override Future<void> setKeyValue<T>(String key, T value) async { final prefs = _instance; switch (T) { case String: await prefs.setString(key, value as String); break; case int: await prefs.setInt(key, value as int); break; case double: await prefs.setDouble(key, value as double); break; case bool: await prefs.setBool(key, value as bool); break; case List: await prefs.setStringList(key, value as List<String>); break; default: throw UnimplementedError('未实现${T.runtimeType}类型的写入逻辑'); } } }
2. 修改main.dart的初始化逻辑
创建全局单例实例,初始化后注入到ProviderScope供全局调用:
void main() async { WidgetsFlutterBinding.ensureInitialized(); await Enviroment.initEnvirontment(); // 创建存储服务实例并完成初始化 final storageService = KeyValueStorageServiceImpl(); await storageService.init(); await initializeDateFormatting('es_ES', null); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) .then((_) => runApp(ProviderScope( overrides: [ // 注入单例,全局可通过ref获取 Provider<KeyValueStorageService>((ref) => storageService), ], child: MyApp(), ))); }
3. UI中处理异步数据的两种可行方式
方式一:使用FutureBuilder直接渲染
在UI组件中用FutureBuilder包裹需要异步数据的部分:
class UserInfoWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final storageService = ref.watch(keyValueStorageServiceProvider); return FutureBuilder<String?>( future: storageService.getValue<String>('user'), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return CircularProgressIndicator(); } final user = snapshot.data; return Text(user ?? '未登录'); }, ); } }
方式二:用Riverpod预加载并缓存数据
创建专门的FutureProvider缓存用户数据,避免UI重建时重复发起请求:
// 定义全局Provider缓存用户数据 final userProvider = FutureProvider<String?>((ref) async { final storageService = ref.watch(keyValueStorageServiceProvider); return storageService.getValue<String>('user'); }); // UI组件中使用 class UserInfoWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final userAsync = ref.watch(userProvider); return userAsync.when( loading: () => CircularProgressIndicator(), error: (err, stack) => Text('加载失败'), data: (user) => Text(user ?? '未登录'), ); } }
关键说明
- 缓存实例:改造后
init()仅执行一次SharedPreferences实例初始化,后续所有业务方法复用该实例,既提升性能又确保提前初始化的效果生效。 - UI异步处理:Flutter的UI渲染是同步流程,必须通过
FutureBuilder或状态管理库的异步Provider来处理Future类型数据,不能直接在build方法内使用await。
内容的提问来源于stack exchange,提问作者Rudy Rosa
相关产品推荐
相关产品推荐

