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

Flutter中如何通过Riverpod组合StateNotifierProvider与FutureProvider

问题核心原因

你当前拆分三个独立Provider的实现存在两个核心缺陷,导致重复触发定位请求:

  1. 独立的currentFetchedLocationProvider只要被重新watch就会执行定位逻辑,没有内置缓存、请求复用判断,只要手动选中的位置被清空触发重建,就会重新调用定位接口,哪怕之前已经获取过有效位置
  2. 在非组件层直接读取currentLocationProvider.future时,没有预先判断Provider当前是否已有缓存值,只要Provider处于非完成状态就会触发新的请求流程,无法复用正在进行的定位请求。
最优实现方案

直接使用Riverpod 2.x的AsyncNotifierProvider统一管理位置状态,不需要拆分多个Provider硬拼逻辑,天然支持AsyncValue的.when()状态区分能力,同时内置请求复用、状态缓存能力,完全匹配你的业务需求。

第一步:实现统一的位置状态Notifier

class CurrentLocationNotifier extends AsyncNotifier<LocationModel?> {
  @override
  Future<LocationModel?> build() async {
    // 初始化优先读取本地持久化的手动设置位置,存在则直接返回,不触发定位
    final savedSelectedLocation = await _loadSavedManualLocation();
    if (savedSelectedLocation != null) {
      return savedSelectedLocation;
    }
    // 无手动设置位置时才发起定位请求
    return ref.read(locationServiceProvider).getCurrentLocation();
  }

  /// 手动设置位置,优先级最高,设置后全局状态立即更新
  Future<void> setManualLocation(LocationModel location) async {
    await _saveManualLocation(location);
    state = AsyncData(location);
  }

  /// 清除手动设置的位置,自动触发重新定位,组件会自动响应loading状态
  Future<void> clearManualLocation() async {
    await _deleteSavedManualLocation();
    state = const AsyncLoading();
    // 用AsyncValue.guard自动捕获定位异常,统一进入error状态
    state = await AsyncValue.guard(
      () => ref.read(locationServiceProvider).getCurrentLocation()
    );
  }

  /// 定位失败重试方法
  Future<void> retryLocation() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(
      () => ref.read(locationServiceProvider).getCurrentLocation()
    );
  }

  // 本地持久化逻辑,根据项目使用的存储方案(SharedPreferences/Hive/Isar等)替换实现即可
  Future<LocationModel?> _loadSavedManualLocation() async {
    // 实现本地读取手动位置的逻辑
  }

  Future<void> _saveManualLocation(LocationModel location) async {
    // 实现本地保存手动位置的逻辑
  }

  Future<void> _deleteSavedManualLocation() async {
    // 实现本地删除手动位置的逻辑
  }
}

// 全局Provider入口
final currentLocationProvider = AsyncNotifierProvider<CurrentLocationNotifier, LocationModel?>(
  CurrentLocationNotifier.new
);

第二步:组件层使用

完全保留你需要的.when()状态区分能力,状态更新自动响应:

@override
Widget build(BuildContext context, WidgetRef ref) {
  final locationState = ref.watch(currentLocationProvider);
  return locationState.when(
    loading: () => const CircularProgressIndicator(),
    error: (error, stackTrace) => Column(
      children: [
        Text('定位失败:$error'),
        ElevatedButton(
          onPressed: () => ref.read(currentLocationProvider.notifier).retryLocation(),
          child: const Text('重试')
        )
      ],
    ),
    data: (location) => Text('当前位置:${location?.address ?? "未知位置"}'),
  );
}

// 手动设置/清除位置的调用示例
// 设置位置
ref.read(currentLocationProvider.notifier).setManualLocation(newLocation);
// 清除位置
ref.read(currentLocationProvider.notifier).clearManualLocation();

第三步:非组件层(如DatabaseService)读取实现

完全满足你的读取要求:有缓存值直接返回不触发请求、请求中复用现有流程等待结果、无有效缓存时才触发新请求:

// 首先通过Provider注入DatabaseService,不要手动实例化传Reader
final databaseServiceProvider = Provider<DatabaseService>((ref) {
  return DatabaseService(ref);
});

class DatabaseService implements BaseDatabaseService {
  final Ref _ref;
  const DatabaseService(this._ref);

  Future<LocationModel?> get currentLocation async {
    // 先读当前同步状态,已有缓存数据直接返回,不触发任何重建/请求
    final currentState = _ref.read(currentLocationProvider);
    if (currentState.hasValue) {
      return currentState.value;
    }
    // 处于loading/error状态时,等待现有请求完成,不会重复发起新定位
    return _ref.read(currentLocationProvider.future);
  }

  Future<Map<String, dynamic>?> getBasicEventJson(String id) async {
    final location = await currentLocation;
    // 后续业务逻辑
  }
}
实现效果验证
  • 手动设置位置优先级最高,设置后所有监听的组件、服务立即拿到最新值,不会触发定位
  • 手动清除位置后,全局自动进入loading状态,定位完成后自动更新为新位置
  • 重复调用currentLocation取值时,只要已经有有效位置就直接返回,不会重复触发定位
  • 定位过程中多次调用取值,只会发起一次定位请求,所有调用方共同等待同一个结果
  • 完整支持loading/data/error三态区分,和普通FutureProvider/AsyncNotifier的使用体验完全一致

内容的提问来源于stack exchange,提问作者Florian Leeser

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 05:00:45