使用Riverpod Provider时如何集成FlutterSecureStorage服务?
问题描述
刚学习Riverpod,想在Provider中使用FlutterSecureStorage,不确定当前实现是否正确。我希望封装一个包含FlutterSecureStorage相关方法的服务,通过Provider实现全应用可访问,以下是我的代码:
storage_service.dart
class StorageService { final _secureStorage = const FlutterSecureStorage(); AndroidOptions _getAndroidOptions() => const AndroidOptions( encryptedSharedPreferences: true, ); Future<void> writeSecureData(StorageItem newItem) async { await _secureStorage.write( key: newItem.key, value: newItem.value, aOptions: _getAndroidOptions()); } ... Future<List<StorageItem>> readAllSecureData() async { var allData = await _secureStorage.readAll(aOptions: _getAndroidOptions()); List<StorageItem> list = allData.entries.map((e) => StorageItem(e.key, e.value)).toList(); return list; } ... } final storageServiceProvider = Provider((ref) => StorageService());
Home Screen代码
class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({super.key}); @override ConsumerState<ConsumerStatefulWidget> createState() => _HomeScreenState(); } class _HomeScreenState extends ConsumerState<HomeScreen> { @override Widget build(BuildContext context) { final storageService = ref.watch(storageServiceProvider); List<StorageItem> data = []; storageService.readAllSecureData().then((value) => data = value); return Scaffold( body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text( 'HomeScreen', ), Expanded( flex: 2, child: ListView.builder( itemCount: data.length, itemBuilder: (context, index) { return Column( children: [ Text('Key: ${data[index].key}'), Text('Value: ${data[index].value}'), ], ); }, ), ), Expanded( flex: 1, child: TextButton( onPressed: () async { final List<StorageItem> st = await storageService.readAllSecureData(); debugPrint('button Pressed ${st.length}'); st.map( (e) => debugPrint('Key: ${e.key} | Value: ${e.value}')); final StorageItem newItem = StorageItem('sup', 'test'); storageService.writeSecureData(newItem); // context.goNamed(AppRoutingConstans.loginScreenRouteName); }, style: TextButton.styleFrom( backgroundColor: Colors.amber, minimumSize: const Size(50, 50), padding: const EdgeInsets.symmetric(horizontal: 148.0), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(5.0)), ), ), child: const Text( 'Go To Login Screen', style: TextStyle(color: Colors.white), ), ), ), ], ), ), ); ; } }
优化建议
你的StorageService和对应的Provider实现是没问题的,核心问题出在HomeScreen的UI更新逻辑上:
直接在build方法里调用异步方法不会触发UI刷新
你在build里通过.then给data赋值,但此时build已经完成,data更新后不会触发setState,ListView不会显示新数据。正确的做法是用FutureProvider或者在State里管理异步状态
方案一:使用FutureProvider获取存储数据
修改Provider,添加一个FutureProvider来获取所有安全存储数据:
// 在storage_service.dart中添加 final allSecureDataProvider = FutureProvider<List<StorageItem>>((ref) async { final storageService = ref.watch(storageServiceProvider); return storageService.readAllSecureData(); });
然后修改HomeScreen的build方法:
@override Widget build(BuildContext context) { final storageService = ref.watch(storageServiceProvider); // 监听FutureProvider的状态 final allDataAsync = ref.watch(allSecureDataProvider); return Scaffold( body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text('HomeScreen'), Expanded( flex: 2, child: allDataAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, stack) => Text('加载失败: $err'), data: (data) { return ListView.builder( itemCount: data.length, itemBuilder: (context, index) { return Column( children: [ Text('Key: ${data[index].key}'), Text('Value: ${data[index].value}'), ], ); }, ); }, ), ), Expanded( flex: 1, child: TextButton( onPressed: () async { final StorageItem newItem = StorageItem('sup', 'test'); await storageService.writeSecureData(newItem); // 写入后刷新FutureProvider的数据 ref.invalidate(allSecureDataProvider); // 打印数据 final st = await storageService.readAllSecureData(); debugPrint('button Pressed ${st.length}'); for (var e in st) { debugPrint('Key: ${e.key} | Value: ${e.value}'); } }, style: TextButton.styleFrom( backgroundColor: Colors.amber, minimumSize: const Size(50, 50), padding: const EdgeInsets.symmetric(horizontal: 148.0), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(5.0)), ), ), child: const Text( 'Go To Login Screen', style: TextStyle(color: Colors.white), ), ), ), ], ), ), ); }
方案二:在State类中管理异步状态
如果不想用FutureProvider,也可以在State里初始化数据并通过setState更新:
class _HomeScreenState extends ConsumerState<HomeScreen> { List<StorageItem> data = []; bool isLoading = true; @override void initState() { super.initState(); _loadData(); } Future<void> _loadData() async { final storageService = ref.read(storageServiceProvider); data = await storageService.readAllSecureData(); isLoading = false; setState(() {}); } @override Widget build(BuildContext context) { final storageService = ref.watch(storageServiceProvider); return Scaffold( body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text('HomeScreen'), Expanded( flex: 2, child: isLoading ? const Center(child: CircularProgressIndicator()) : ListView.builder( itemCount: data.length, itemBuilder: (context, index) { return Column( children: [ Text('Key: ${data[index].key}'), Text('Value: ${data[index].value}'), ], ); }, ), ), Expanded( flex: 1, child: TextButton( onPressed: () async { final StorageItem newItem = StorageItem('sup', 'test'); await storageService.writeSecureData(newItem); // 写入后重新加载数据并更新UI await _loadData(); // 打印数据 final st = await storageService.readAllSecureData(); debugPrint('button Pressed ${st.length}'); for (var e in st) { debugPrint('Key: ${e.key} | Value: ${e.value}'); } }, style: TextButton.styleFrom( backgroundColor: Colors.amber, minimumSize: const Size(50, 50), padding: const EdgeInsets.symmetric(horizontal: 148.0), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(5.0)), ), ), child: const Text( 'Go To Login Screen', style: TextStyle(color: Colors.white), ), ), ), ], ), ), ); } }
其他注意点
- 确保
StorageItem类正确实现,包含key和value字段。 - 在写入数据后,需要手动触发数据刷新(方案一中用
ref.invalidate,方案二中重新调用_loadData),这样UI才能显示最新的存储内容。 - 原代码中
st.map(...)不会执行,因为map是惰性的,需要用forEach或者遍历打印。
内容的提问来源于stack exchange,提问作者DarkSide77
相关产品推荐
相关产品推荐

