Flutter中基于Riverpod实现CoinGecko API定时数据拉取的方案咨询
解决方案:用Riverpod实现加密货币数据定时刷新
问题分析
你当前使用的FutureProvider仅能发起单次异步请求,它的设计定位就是处理一次性异步操作,无法自动重复执行请求。要实现每15秒刷新数据并更新UI,需要选择能管理持续异步状态的Provider,并结合定时器逻辑封装刷新规则。
最优实现方案:使用AsyncNotifierProvider(Riverpod 2.x)
AsyncNotifierProvider是Riverpod 2.x中专门用于管理异步状态的Provider,它允许你集中封装异步请求、定时刷新逻辑,同时支持手动触发刷新,状态管理更整洁。
步骤1:实现AsyncNotifier
创建继承自AsyncNotifier的类,把数据请求、定时刷新逻辑都封装进去:
import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; class CoinNotifier extends AsyncNotifier<List<Coin>> { Timer? _refreshTimer; // 定义刷新间隔为15秒 final _refreshInterval = const Duration(seconds: 15); // 初始化时立即拉取数据并启动定时器 @override Future<List<Coin>> build() async { _startRefreshTimer(); return await _fetchCoinData(); } // 封装数据请求逻辑 Future<List<Coin>> _fetchCoinData() async { final resp = await http.get(Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=10&page=1&sparkline=false&price_change_percentage=24h")); if (resp.statusCode == 200) { List<dynamic> decoded = json.decode(resp.body); return decoded.map((coin) => Coin.fromJson(coin)).toList(); } else { throw Exception('请求失败'); } } // 启动定时刷新定时器 void _startRefreshTimer() { _refreshTimer?.cancel(); // 避免重复创建定时器 _refreshTimer = Timer.periodic(_refreshInterval, (timer) async { // 先设置为加载状态 state = const AsyncValue.loading(); try { final data = await _fetchCoinData(); state = AsyncValue.data(data); } catch (e, stack) { state = AsyncValue.error(e, stack); } }); } // 手动触发刷新(可选,比如下拉刷新时调用) Future<void> refresh() async { state = const AsyncValue.loading(); try { final data = await _fetchCoinData(); state = AsyncValue.data(data); } catch (e, stack) { state = AsyncValue.error(e, stack); } } // 销毁Provider时取消定时器,防止内存泄漏 @override void dispose() { _refreshTimer?.cancel(); super.dispose(); } } // 定义全局Provider final marketProvider = AsyncNotifierProvider<CoinNotifier, List<Coin>>(() => CoinNotifier());
步骤2:更新页面代码
页面逻辑基本不变,保持ref.watch监听Provider即可,状态更新时UI会自动重建:
class CoinListPage extends ConsumerWidget { const CoinListPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { AsyncValue<List<Coin>> coinList = ref.watch(marketProvider); // 可选:添加下拉刷新功能 return Scaffold( appBar: AppBar( title: const Text("加密货币列表"), ), body: RefreshIndicator( onRefresh: () => ref.read(marketProvider.notifier).refresh(), child: Center( child: coinList.when( data: (data) => CoinList(coinList: data), error: (e, stack) => Text('错误: $e'), loading: () => const CircularProgressIndicator(), ), ), ), ); } }
为什么这是最优方案?
- 职责分离:异步请求、定时刷新逻辑都集中在
CoinNotifier中,页面只负责UI渲染,代码结构清晰易维护。 - 生命周期安全:定时器在Provider初始化时启动,销毁时自动取消,避免内存泄漏。
- 灵活性高:同时支持自动定时刷新和手动触发刷新(如下拉刷新场景)。
- 状态管理优雅:
AsyncValue自动处理加载、成功、错误三种状态,无需手动维护多个状态变量。
其他可选方案(不推荐)
如果使用Riverpod 1.x,你可以用StateProvider<AsyncValue<List<Coin>>>结合页面内的定时器实现,但这种方式会把异步逻辑和定时器逻辑分散在页面中,代码冗余且不易维护,远不如AsyncNotifierProvider优雅。
内容的提问来源于stack exchange,提问作者Arjein
相关产品推荐
相关产品推荐

