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

Drawer重复调用FirstPage时FutureProvider不触发加载状态的问题

问题:从Drawer重复打开同页面时触发FutureProvider重新加载并显示Loading状态

点击Drawer中的选项打开FirstPage页面,首次打开时,由于页面不在Widget Tree中,FutureProvider会进入Loading模式,获取数据后显示页面内容。但当FirstPage已显示时,再次从Drawer调用该页面,FutureProvider不会重新获取数据——推测是FirstPage仍在Widget Tree中未被自动释放。尝试在Drawer跳转时执行ref.invalidate(helloProvider),虽能重新获取数据,但不会触发Loading状态,期望每次跳转都能完整走一遍AsyncValue的loading→data流程。

最小可复现代码

main.dart

import 'package:drawer_refresh_sample/drawer.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: const SampleDrawer(),
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(title),
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Good Morning',
            ),
          ],
        ),
      ),
    );
  }
}

drawer.dart

import 'package:drawer_refresh_sample/first_page.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class SampleDrawer extends ConsumerWidget {
  const SampleDrawer({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Drawer(
      child: ListView(
        children: <Widget>[
          ListTile(
            title: const Text("Page 1"),
            trailing: const Icon(Icons.arrow_forward),
            onTap: () {
              Navigator.of(context).pop();
              ref.invalidate(helloProvider);
              Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const FirstPage()));
            },
          ),
        ],
      ),
    );
  }
}

first_page.dart

import 'package:drawer_refresh_sample/sample_provider.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import 'drawer.dart';

class FirstPage extends ConsumerWidget {
  const FirstPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final asyncHello = ref.watch(helloProvider);
    return asyncHello.when(
        data: (data) => Scaffold(
            appBar: AppBar(
              title: const Text("First Page"),
            ),
            drawer: const SampleDrawer(),
            body: SafeArea(child: Center(child: Text(data)))),
        error: (error, stackTrace) => Text(stackTrace.toString()),
        loading: () => const Center(child: CircularProgressIndicator.adaptive()));
  }
}

sample_provider.dart

import "dart:math";

import 'package:flutter/material.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'sample_provider.g.dart';

@riverpod
Future<String> hello(HelloRef ref) {
  return Future<String>.delayed(const Duration(seconds: 2), () {
    return "Hello${Random().nextInt(10)}";
  });
}

通用解决方案

方案1:用ref.refresh替代ref.invalidate

ref.refresh会强制重新执行Provider的异步逻辑,并且主动触发Loading状态,和首次加载行为一致。修改Drawer的onTap逻辑:

onTap: () {
  Navigator.of(context).pop();
  // 强制触发重新加载并进入loading状态
  ref.refresh(helloProvider);
  Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const FirstPage()));
},

原理:refresh会主动运行Provider创建函数,让所有监听组件立即收到loading状态;而invalidate仅标记Provider需重建,若有活跃监听会在后台更新,不会触发loading。

方案2:移除旧页面后再跳转

通过清空导航栈中旧的FirstPage实例,确保每次打开的都是全新页面,自然触发首次加载的Loading流程:

onTap: () {
  Navigator.of(context).pop(); // 关闭Drawer
  // 回到根页面,确保旧FirstPage被移除出Widget Tree
  Navigator.of(context).popUntil((route) => route.isFirst);
  Navigator.of(context).push(MaterialPageRoute(builder: (_) => const FirstPage()));
},

注意:此方案会改变导航栈结构,需根据业务场景选择。

方案3:用唯一Key强制重建页面

通过给FirstPage传入不同的Key,让Flutter认为是新Widget,从而销毁旧实例、创建新实例,重新触发Provider监听和Loading流程:

首先修改FirstPage:

class FirstPage extends ConsumerWidget {
  const FirstPage({super.key});
  // 新增带Key的构造方法
  FirstPage.withUniqueKey() : super(key: UniqueKey());

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 原build逻辑不变
  }
}

再修改Drawer的onTap:

onTap: () {
  Navigator.of(context).pop();
  // 每次传入新的UniqueKey,强制重建页面
  Navigator.of(context).pushReplacement(MaterialPageRoute(
    builder: (_) => FirstPage.withUniqueKey(),
  ));
},

原理:Flutter通过Key识别Widget身份,不同Key会触发Widget实例的重建,新实例重新watch Provider时会触发Loading。

方案4:自定义刷新触发器

添加一个StateProvider作为刷新信号,让helloProvider依赖该信号,每次触发信号时重新执行异步逻辑并显示Loading:

// 新增刷新触发器
final helloRefreshTrigger = StateProvider<int>((ref) => 0);

@riverpod
Future<String> hello(HelloRef ref) {
  // 监听触发器,状态变化时重新执行
  ref.watch(helloRefreshTrigger);
  return Future<String>.delayed(const Duration(seconds: 2), () {
    return "Hello${Random().nextInt(10)}";
  });
}

修改Drawer的onTap:

onTap: () {
  Navigator.of(context).pop();
  // 递增触发器状态,触发刷新
  ref.read(helloRefreshTrigger.notifier).state++;
  Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const FirstPage()));
},

优势:灵活可控,适合需要多处触发页面刷新的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 12:22:03