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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 08:02:02