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

使用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更新逻辑上:

  1. 直接在build方法里调用异步方法不会触发UI刷新
    你在build里通过.then给data赋值,但此时build已经完成,data更新后不会触发setState,ListView不会显示新数据。

  2. 正确的做法是用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 17:17:58